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:
parent
b5f85637a7
commit
8aeb3eaec4
34 changed files with 1752 additions and 341 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue