Merge branch 'oauth2'

This commit is contained in:
Morgan Bazalgette 2017-07-25 10:41:09 +02:00
commit 60d48df46d
No known key found for this signature in database
GPG Key ID: 40D328300D245DA5
6 changed files with 151 additions and 10 deletions

View File

@ -5,6 +5,7 @@ import (
"fmt"
"reflect"
"regexp"
"strings"
"unsafe"
"github.com/valyala/fasthttp"
@ -23,9 +24,13 @@ func initialCaretaker(c *fasthttp.RequestCtx, f func(md common.MethodData) commo
qa := c.Request.URI().QueryArgs()
var token string
var bearerToken bool
switch {
case len(c.Request.Header.Peek("X-Ripple-Token")) > 0:
token = string(c.Request.Header.Peek("X-Ripple-Token"))
case strings.HasPrefix(string(c.Request.Header.Peek("Authorization")), "Bearer "):
token = strings.TrimPrefix(string(c.Request.Header.Peek("Authorization")), "Bearer ")
bearerToken = true
case len(qa.Peek("token")) > 0:
token = string(qa.Peek("token"))
case len(qa.Peek("k")) > 0:
@ -41,7 +46,15 @@ func initialCaretaker(c *fasthttp.RequestCtx, f func(md common.MethodData) commo
R: red,
}
if token != "" {
tokenReal, exists := GetTokenFull(token, db)
var (
tokenReal common.Token
exists bool
)
if bearerToken {
tokenReal, exists = BearerToken(token, db)
} else {
tokenReal, exists = GetTokenFull(token, db)
}
if exists {
md.User = tokenReal
doggoTags = append(doggoTags, "authorised")

View File

@ -79,8 +79,10 @@ func Start(conf common.Conf, dbO *sqlx.DB) *fhr.Router {
// v1 API
{
r.POSTMethod("/api/v1/tokens", v1.TokenNewPOST)
r.POSTMethod("/api/v1/tokens/new", v1.TokenNewPOST)
// These require an user to pass the password in cleartext and are
// as such really insecure.
//r.POSTMethod("/api/v1/tokens", v1.TokenNewPOST)
//r.POSTMethod("/api/v1/tokens/new", v1.TokenNewPOST)
r.POSTMethod("/api/v1/tokens/self/delete", v1.TokenSelfDeletePOST)
// Auth-free API endpoints (public data)

View File

@ -2,12 +2,14 @@ package app
import (
"crypto/md5"
"crypto/sha256"
"database/sql"
"fmt"
"strings"
"time"
"zxq.co/ripple/rippleapi/common"
"github.com/jmoiron/sqlx"
"zxq.co/ripple/rippleapi/common"
)
// GetTokenFull retrieves an user ID and their token privileges knowing their API token.
@ -18,7 +20,7 @@ func GetTokenFull(token string, db *sqlx.DB) (common.Token, bool) {
userPrivsRaw uint64
priv8 bool
)
err := db.QueryRow(`SELECT
err := db.QueryRow(`SELECT
t.id, t.user, t.privileges, t.private, u.privileges
FROM tokens t
LEFT JOIN users u ON u.id = t.user
@ -84,3 +86,41 @@ func tokenUpdater(db *sqlx.DB) {
}
}
}
// BearerToken parses a Token guiven in the Authorization header, with the
// Bearer prefix.
func BearerToken(token string, db *sqlx.DB) (common.Token, bool) {
var x struct {
Scope string
Extra int
}
db.Get(&x, "SELECT scope, extra FROM osin_access WHERE access_token = ? LIMIT 1", fmt.Sprintf("%x", sha256.Sum256([]byte(token))))
if x.Extra == 0 {
return common.Token{}, false
}
var privs uint64
db.Get(&privs, "SELECT privileges FROM users WHERE id = ? LIMIT 1", x.Extra)
var t common.Token
t.ID = -1
t.UserID = x.Extra
t.Value = token
t.UserPrivileges = common.UserPrivileges(privs)
t.TokenPrivileges = oauthPrivileges(x.Scope).CanOnly(t.UserPrivileges)
return t, true
}
var privilegeMap = map[string]common.Privileges{
"read_confidential": common.PrivilegeReadConfidential,
"write": common.PrivilegeWrite,
}
func oauthPrivileges(scopes string) common.Privileges {
var p common.Privileges
for _, x := range strings.Split(scopes, " ") {
p |= privilegeMap[x]
}
return p
}

View File

@ -79,6 +79,12 @@ var randomSentences = [...]string{
"Superman dies",
"PP when?",
"RWC hype",
"I'd just like to interject for a moment.",
"Running on an apple pie!",
":thinking:",
"The total entropy of an isolated system can only increase over time",
"Where are my testicles, Summer?",
"Why don't you ask the smartest people in the universe? Oh yeah, you can't. They blew up.",
}
func surpriseMe() string {

View File

@ -2,7 +2,10 @@ package v1
import (
"crypto/md5"
"crypto/sha256"
"database/sql"
"encoding/json"
"errors"
"fmt"
"strconv"
"time"
@ -139,8 +142,14 @@ func TokenSelfDeletePOST(md common.MethodData) common.CodeMessager {
if md.ID() == 0 {
return common.SimpleResponse(400, "How should we delete your token if you haven't even given us one?!")
}
_, err := md.DB.Exec("DELETE FROM tokens WHERE token = ? LIMIT 1",
fmt.Sprintf("%x", md5.Sum([]byte(md.User.Value))))
var err error
if md.IsBearer() {
_, err = md.DB.Exec("DELETE FROM osin_access WHERE access_token = ? LIMIT 1",
fmt.Sprintf("%x", sha256.Sum256([]byte(md.User.Value))))
} else {
_, err = md.DB.Exec("DELETE FROM tokens WHERE token = ? LIMIT 1",
fmt.Sprintf("%x", md5.Sum([]byte(md.User.Value))))
}
if err != nil {
md.Err(err)
return Err500
@ -167,7 +176,8 @@ func TokenGET(md common.MethodData) common.CodeMessager {
}
rows, err := md.DB.Query("SELECT id, privileges, description, last_updated FROM tokens "+
wc.Clause+common.Paginate(md.Query("p"), md.Query("l"), 50), wc.Params...)
if err != nil {
if err != nil {
return Err500
}
var r tokenResponse
@ -184,20 +194,66 @@ func TokenGET(md common.MethodData) common.CodeMessager {
return r
}
type oauthClient struct {
ID string `json:"id"`
Name string `json:"name"`
OwnerID int `json:"owner_id"`
Avatar string `json:"avatar"`
}
// Scan scans the extra in the mysql table into Name, OwnerID and Avatar.
func (o *oauthClient) Scan(src interface{}) error {
var s []byte
switch x := src.(type) {
case string:
s = []byte(x)
case []byte:
s = x
default:
return errors.New("Can't scan non-string")
}
var vals [3]string
err := json.Unmarshal(s, &vals)
if err != nil {
return err
}
o.Name = vals[0]
o.OwnerID, _ = strconv.Atoi(vals[1])
o.Avatar = vals[2]
return nil
}
type bearerToken struct {
Client oauthClient `json:"client"`
Scope string `json:"scope"`
Privileges common.Privileges `json:"privileges"`
Created time.Time `json:"created"`
}
type tokenSingleResponse struct {
common.ResponseBase
token
}
type bearerTokenSingleResponse struct {
common.ResponseBase
bearerToken
}
// TokenSelfGET retrieves information about the token the user is connecting with.
func TokenSelfGET(md common.MethodData) common.CodeMessager {
if md.ID() == 0 {
return common.SimpleResponse(404, "How are we supposed to find the token you're using if you ain't even using one?!")
}
if md.IsBearer() {
return getBearerToken(md)
}
var r tokenSingleResponse
// md.User.ID = token id, userid would have been md.User.UserID. what a clusterfuck
err := md.DB.QueryRow("SELECT id, privileges, description, last_updated FROM tokens WHERE id = ? "+
common.Paginate(md.Query("p"), md.Query("l"), 50), md.User.ID).Scan(
err := md.DB.QueryRow("SELECT id, privileges, description, last_updated FROM tokens WHERE id = ?", md.User.ID).Scan(
&r.ID, &r.Privileges, &r.Description, &r.LastUpdated,
)
if err != nil {
@ -208,6 +264,25 @@ func TokenSelfGET(md common.MethodData) common.CodeMessager {
return r
}
func getBearerToken(md common.MethodData) common.CodeMessager {
var b bearerTokenSingleResponse
err := md.DB.
QueryRow(`
SELECT t.scope, t.created_at, c.id, c.extra
FROM osin_access t INNER JOIN osin_client c
WHERE t.access_token = ?
`, fmt.Sprintf("%x", sha256.Sum256([]byte(md.User.Value)))).Scan(
&b.Scope, &b.Created, &b.Client.ID, &b.Client,
)
if err != nil {
md.Err(err)
return Err500
}
b.Code = 200
b.Privileges = md.User.TokenPrivileges
return b
}
// TokenFixPrivilegesPOST fixes the privileges on the token of the given user,
// or of all the users if no user is given.
func TokenFixPrivilegesPOST(md common.MethodData) common.CodeMessager {

View File

@ -143,3 +143,8 @@ func (md MethodData) HasQuery(q string) bool {
func (md MethodData) Unmarshal(into interface{}) error {
return json.Unmarshal(md.Ctx.PostBody(), into)
}
// IsBearer tells whether the current token is a Bearer (oauth) token.
func (md MethodData) IsBearer() bool {
return md.User.ID == -1
}