1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-25 08:09:43 +02:00

upgraded vendored jwt-go lib

This commit is contained in:
Harvey Kandola 2017-03-17 11:01:42 +00:00
parent b5f85637a7
commit 8aeb3eaec4
34 changed files with 1752 additions and 341 deletions

View file

@ -13,7 +13,6 @@ package endpoint
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"net/http"
"strings"
@ -56,18 +55,14 @@ func init() {
// Generates JSON Web Token (http://jwt.io)
func generateJWT(user, org, domain string) string {
token := jwt.New(jwt.SigningMethodHS256)
// issuer
token.Claims["iss"] = "Documize"
// subject
token.Claims["sub"] = "webapp"
// expiry
token.Claims["exp"] = time.Now().Add(time.Hour * 168).Unix()
// data
token.Claims["user"] = user
token.Claims["org"] = org
token.Claims["domain"] = domain
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": "Documize",
"sub": "webapp",
"exp": time.Now().Add(time.Hour * 168).Unix(),
"user": user,
"org": org,
"domain": domain,
})
tokenString, _ := token.SignedString([]byte(jwtKey))
@ -98,7 +93,7 @@ func findJWT(r *http.Request) (token string) {
}
// We take in raw token string and decode it.
func decodeJWT(tokenString string) (c request.Context, claims map[string]interface{}, err error) {
func decodeJWT(tokenString string) (c request.Context, claims jwt.Claims, err error) {
method := "decodeJWT"
// sensible defaults
@ -139,8 +134,13 @@ func decodeJWT(tokenString string) (c request.Context, claims map[string]interfa
}
c = request.NewContext()
c.UserID = token.Claims["user"].(string)
c.OrgID = token.Claims["org"].(string)
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
c.UserID = claims["user"].(string)
c.OrgID = claims["org"].(string)
} else {
fmt.Println(err)
}
if len(c.UserID) == 0 || len(c.OrgID) == 0 {
err = fmt.Errorf("%s : unable parse token data", method)
@ -154,71 +154,18 @@ func decodeJWT(tokenString string) (c request.Context, claims map[string]interfa
}
// We take in Keycloak token string and decode it.
func decodeKeycloakJWT(t, pk string) (err error) {
// method := "decodeKeycloakJWT"
func decodeKeycloakJWT(t, pk string) (c jwt.MapClaims, err error) {
token, err := jwt.Parse(t, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
log.Info(t)
log.Info(pk)
return jwt.ParseRSAPublicKeyFromPEM([]byte(pk))
})
var rsaPSSKey *rsa.PublicKey
if rsaPSSKey, err = jwt.ParseRSAPublicKeyFromPEM([]byte(pk)); err != nil {
log.Error("Unable to parse RSA public key", err)
return
if c, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
return c, nil
}
parts := strings.Split(t, ".")
m := jwt.GetSigningMethod("RSA256")
err = m.Verify(strings.Join(parts[0:2], "."), parts[2], rsaPSSKey)
if err != nil {
log.Error("Error while verifying key", err)
return
}
// token, err := jwt.Parse(t, func(token *jwt.Token) (interface{}, error) {
// p, pe := jwt.ParseRSAPublicKeyFromPEM([]byte(pk))
// if pe != nil {
// log.Error("have jwt err", pe)
// }
// return p, pe
// // return []byte(jwtKey), nil
// })
// if err != nil {
// err = fmt.Errorf("bad authorization token")
// return
// }
// if !token.Valid {
// if ve, ok := err.(*jwt.ValidationError); ok {
// if ve.Errors&jwt.ValidationErrorMalformed != 0 {
// log.Error("invalid token", err)
// err = fmt.Errorf("bad token")
// return
// } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// log.Error("expired token", err)
// err = fmt.Errorf("expired token")
// return
// } else {
// log.Error("invalid token", err)
// err = fmt.Errorf("bad token")
// return
// }
// } else {
// log.Error("invalid token", err)
// err = fmt.Errorf("bad token")
// return
// }
// }
// email := token.Claims["user"].(string)
// exp := token.Claims["exp"].(string)
// sub := token.Claims["sub"].(string)
// if len(email) == 0 || len(exp) == 0 || len(sub) == 0 {
// err = fmt.Errorf("%s : unable parse Keycloak token data", method)
// return
// }
return nil
return nil, err
}

View file

@ -14,23 +14,20 @@ package endpoint
import (
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/documize/community/core/api/endpoint/models"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/api/util"
"github.com/documize/community/core/log"
// "github.com/documize/community/core/section/provider"
"github.com/documize/community/core/utility"
)
// AuthenticateKeycloak checks Keycloak authentication credentials.
//
// TODO:
// 1. validate keycloak token
// 2. implement new user additions: user & account with RefID
//
func AuthenticateKeycloak(w http.ResponseWriter, r *http.Request) {
method := "AuthenticateKeycloak"
p := request.GetPersister(r)
@ -49,7 +46,6 @@ func AuthenticateKeycloak(w http.ResponseWriter, r *http.Request) {
return
}
// Clean data.
a.Domain = strings.TrimSpace(strings.ToLower(a.Domain))
a.Domain = request.CheckDomain(a.Domain) // TODO optimize by removing this once js allows empty domains
a.Email = strings.TrimSpace(strings.ToLower(a.Email))
@ -60,27 +56,45 @@ func AuthenticateKeycloak(w http.ResponseWriter, r *http.Request) {
return
}
// Validate Keycloak credentials
pks := "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1NSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQTAwRzI3KzZYNzJFWllIY3NyY1pHekYwZzFsL1gzeVdLS20vZ3NnMCtjMWdXQ2R4ZmI4QmtkbFdCcXhXZVRoSEZCVUVETnorakFyTjBlL0dFMXorMmxnQzJlMkQwemFlcjdlSHZ6bzlBK1hkb0h4KzRNS3RUbkxZZS9aYUFpc3ExSHVURkRKZElKZFRJVUpTWUFXZlNrSmJtdGhIOUVPMmF3SVhEQzlMMWpDa2IwNHZmZ0xERFA3bVo1YzV6NHJPcGluTU45V3RkSm8xeC90VG0xVDlwRHQ3NDRIUHBoMENSbW5OcTRCdWo2SGpaQ3hCcFF1aUp5am0yT0lIdm4vWUJxVUlSUitMcFlJREV1d2FQRU04QkF1eWYvU3BBTGNNaG9oZndzR255QnFMV3QwVFBua3plZjl6ZWN3WEdsQXlYbUZCWlVkR1k3Z0hOdDRpVmdvMXp5d0lEQVFBQi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ=="
pkb, err := utility.DecodeBase64([]byte(pks))
org, err := p.GetOrganizationByDomain(a.Domain)
if err != nil {
log.Error("", err)
writeBadRequestError(w, method, "Unable to decode authentication token")
writeUnauthorizedError(w)
return
}
p.Context.OrgID = org.RefID
// Fetch Keycloak auth provider config
ac := keycloakConfig{}
err = json.Unmarshal([]byte(org.AuthConfig), &ac)
if err != nil {
writeBadRequestError(w, method, "Unable to unmarshall Keycloak Public Key")
return
}
// Decode and prepare RSA Public Key used by keycloak to sign JWT.
pkb, err := utility.DecodeBase64([]byte(ac.PublicKey))
if err != nil {
writeBadRequestError(w, method, "Unable to base64 decode Keycloak Public Key")
return
}
pk := string(pkb)
pk = `
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA00G27+6X72EZYHcsrcZGzF0g1l/X3yWKKm/gsg0+c1gWCdxfb8BkdlWBqxWeThHFBUEDNz+jArN0e/GE1z+2lgC2e2D0zaer7eHvzo9A+XdoHx+4MKtTnLYe/ZaAisq1HuTFDJdIJdTIUJSYAWfSkJbmthH9EO2awIXDC9L1jCkb04vfgLDDP7mZ5c5z4rOpinMN9WtdJo1x/tTm1T9pDt744HPph0CRmnNq4Buj6HjZCxBpQuiJyjm2OIHvn/YBqUIRR+LpYIDEuwaPEM8BAuyf/SpALcMhohfwsGnyBqLWt0TPnkzef9zecwXGlAyXmFBZUdGY7gHNt4iVgo1zywIDAQAB
-----END PUBLIC KEY-----
`
pk = fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", pk)
err = decodeKeycloakJWT(a.Token, pk)
// Decode and verify Keycloak JWT
claims, err := decodeKeycloakJWT(a.Token, pk)
if err != nil {
writeServerError(w, method, err)
return
}
// Compare the contents from JWT with what we have.
// Guards against MITM token tampering.
if a.Email != claims["email"].(string) || claims["sub"].(string) != a.RemoteID {
writeUnauthorizedError(w)
return
}
log.Info("keycloak logon attempt " + a.Email + " @ " + a.Domain)
user, err := p.GetUserByDomain(a.Domain, a.Email)
@ -99,6 +113,11 @@ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA00G27+6X72EZYHcsrcZGzF0g1l/X3yWKKm/g
return
}
user, err = addUser(p, a)
if err != nil {
writeServerError(w, method, err)
return
}
}
// Password correct and active user
@ -107,12 +126,6 @@ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA00G27+6X72EZYHcsrcZGzF0g1l/X3yWKKm/g
return
}
org, err := p.GetOrganizationByDomain(a.Domain)
if err != nil {
writeUnauthorizedError(w)
return
}
// Attach user accounts and work out permissions.
attachUserAccounts(p, org.RefID, &user)
@ -148,6 +161,85 @@ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA00G27+6X72EZYHcsrcZGzF0g1l/X3yWKKm/g
writeSuccessBytes(w, json)
}
// Helper method to setup user account in Documize using Keycloak provided user data.
func addUser(p request.Persister, a keycloakAuthRequest) (u entity.User, err error) {
u.Firstname = a.Firstname
u.Lastname = a.Lastname
u.Email = a.Email
u.Initials = utility.MakeInitials(a.Firstname, a.Lastname)
u.Salt = util.GenerateSalt()
u.Password = util.GeneratePassword(util.GenerateRandomPassword(), u.Salt)
// only create account if not dupe
addUser := true
addAccount := true
var userID string
userDupe, err := p.GetUserByEmail(a.Email)
if err != nil && err != sql.ErrNoRows {
return u, err
}
if u.Email == userDupe.Email {
addUser = false
userID = userDupe.RefID
}
p.Context.Transaction, err = request.Db.Beginx()
if err != nil {
return u, err
}
if addUser {
userID = util.UniqueID()
u.RefID = userID
err = p.AddUser(u)
if err != nil {
log.IfErr(p.Context.Transaction.Rollback())
return u, err
}
} else {
attachUserAccounts(p, p.Context.OrgID, &userDupe)
for _, a := range userDupe.Accounts {
if a.OrgID == p.Context.OrgID {
addAccount = false
break
}
}
}
// set up user account for the org
if addAccount {
var a entity.Account
a.UserID = userID
a.OrgID = p.Context.OrgID
a.Editor = true
a.Admin = false
accountID := util.UniqueID()
a.RefID = accountID
a.Active = true
err = p.AddAccount(a)
if err != nil {
log.IfErr(p.Context.Transaction.Rollback())
return u, err
}
}
log.IfErr(p.Context.Transaction.Commit())
// If we did not add user or give them access (account) then we error back
if !addUser && !addAccount {
log.IfErr(p.Context.Transaction.Rollback())
return u, err
}
return p.GetUser(userID)
}
// Data received via Keycloak client library
type keycloakAuthRequest struct {
Domain string `json:"domain"`