Skip to content

Commit

Permalink
implement tools and test for tools: slices and maps
Browse files Browse the repository at this point in the history
  • Loading branch information
erudenko committed Jul 11, 2023
1 parent 074186d commit 250e19f
Show file tree
Hide file tree
Showing 14 changed files with 421 additions and 95 deletions.
57 changes: 48 additions & 9 deletions jwt/service/jwt_token_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,53 @@ func (ts *JWTokenService) Issuer() string {
return ts.issuer
}

// CreateToken creates token with specific types.
func (ts *JWTokenService) NewToken(tokenType model.TokenType, userID string, payload []any) (model.Token, error) {
// TODO: implement general token creation for all token types
return &model.JWToken{}, nil
func (ts *JWTokenService) NewToken(tokenType model.TokenType, u model.User, fields []string, payload map[string]any) (model.Token, error) {
payload := make(map[string]any)
jwt.StandardClaims
if model.SliceContains(app.TokenPayload, PayloadName) {
payload[PayloadName] = u.Username
}

tokenType := model.TokenTypeAccess
if len(tokenPayload) > 0 {
for k, v := range tokenPayload {
payload[k] = v
}
}

now := ijwt.TimeFunc().Unix()

lifespan := app.TokenLifespan
if lifespan == 0 {
lifespan = TokenLifespan
}

claims := model.Claims{
Scopes: strings.Join(scopes, " "),
Payload: payload,
Type: string(tokenType),
StandardClaims: jwt.StandardClaims{
ExpiresAt: (now + lifespan),
Issuer: ts.issuer,
Subject: u.ID,
Audience: app.ID,
IssuedAt: now,
},
}

sm := ts.jwtMethod()
if sm == nil {
return nil, errors.New("unable to creating signing method")
}

token := model.NewTokenWithClaims(sm, ts.KeyID(), claims)
if token == nil {
return nil, ErrCreatingToken
}
return &model.JWToken{JWT: token, New: true}, nil
}

func (ts *JWTokenService) SignToken(token model.Token) (string, error) {
}

// Algorithm returns signature algorithm.
Expand Down Expand Up @@ -147,17 +190,13 @@ func (ts *JWTokenService) PublicKey() interface{} {
return ts.cachedPublicKey
}

func (ts *JWTokenService) SetPrivateKey(key interface{}) {
func (ts *JWTokenService) SetPrivateKey(key any) {
fmt.Printf("Changing private key for Token service, all new tokens will be signed with a new key!!!\n")
ts.privateKey = key
ts.cachedPublicKey = nil
ts.cachedAlgorithm = ""
}

func (ts *JWTokenService) PrivateKey() interface{} {
return ts.privateKey
}

// KeyID returns public key ID, using SHA-1 fingerprint.
func (ts *JWTokenService) KeyID() string {
pk := ts.PublicKey()
Expand Down
15 changes: 14 additions & 1 deletion model/token.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package model

import (
"encoding/json"
"time"

jwt "github.com/golang-jwt/jwt/v4"
Expand Down Expand Up @@ -50,7 +51,7 @@ type Token interface {
// NewTokenWithClaims generates new JWT token with claims and keyID.
func NewTokenWithClaims(method jwt.SigningMethod, kid string, claims jwt.Claims) *jwt.Token {
return &jwt.Token{
Header: map[string]interface{}{
Header: map[string]any{
"typ": "JWT",
"alg": method.Alg(),
"kid": kid,
Expand Down Expand Up @@ -189,5 +190,17 @@ type Claims struct {
jwt.StandardClaims
}

func (c *Claims) SC() *jwt.StandardClaims {
return &c.StandardClaims
}

func (c *Claims) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
*jwt.StandardClaims
}{
StandardClaims: &c.StandardClaims,
})
}

// Full example of how to use JWT tokens:
// https://github.com/form3tech-oss/jwt-go/blob/master/cmd/jwt/app.go
5 changes: 2 additions & 3 deletions model/token_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ type TokenService interface {

// keys management
// replace the old private key with a new one
SetPrivateKey(key interface{})
PrivateKey() interface{}
SetPrivateKey(key any)

// not using crypto.PublicKey here to avoid dependencies
PublicKey() interface{}
PublicKey() any
KeyID() string
}
4 changes: 2 additions & 2 deletions model/user_scopes.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const (
IDTokenScope = "id"
TenantScope = "tenant"
AccessTokenScopePrefix = "access:"
TenantScopePrefix = "tenant:" // tenant:123 - request tenant data only for tenant 123
TenantScopeAll = "all" // "tenant:all" - return all scopes for all ten
TenantScopePrefix = "tenant:" // tenant:123 - request tenant data only for tenant 123
TenantScopeAll = "tenant:all" // "tenant:all" - return all scopes for all ten
)

func FieldsetForScopes(scopes []string) []string {
Expand Down
58 changes: 0 additions & 58 deletions server/controller/slices.go

This file was deleted.

65 changes: 60 additions & 5 deletions server/controller/user_controller_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,36 @@ import (
"strings"

"github.com/madappgang/identifo/v2/model"
"golang.org/x/exp/maps"
)

// TODO! we need to add tenant related information flattered, as:
// "112233:admin:user", where 112233 - tenant ID, admin - a group, user - role in a group
// tenant related information flattered, as:
// "112233:admin" : "user", where 112233 - tenant ID, admin - a group, user - role in a group
// and tenant name added as well:
// "tenant:112233": "tenant corporation"
func (c *UserStorageController) getJWTTokens(ctx context.Context, app model.AppData, u model.User, scopes []string) (model.AuthResponse, error) {
// check if we are
var err error

// TODO: implement custom payload provider for app
resp := model.AuthResponse{}
ud := model.UserData{}

ap := AccessTokenScopes(scopes) // fields for access token
apf := model.FieldsetForScopes(scopes)
data := map[string]any{}

at, err := c.ts.NewToken(model.TokenTypeAccess, u, apf, nil)
// access token needs tenant data in it
if needTenantInfo(ap) {
ud, err = c.u.UserData(ctx, u.ID, model.UserDataFieldTenantMembership)
if err != nil {
return resp, err
}
ti := TenantData(ud.TenantMembership, scopes)
maps.Copy(data, ti)
}
// create access token
at, err := c.ts.NewToken(model.TokenTypeAccess, u, apf, data)
if err != nil {
return resp, err
}
Expand All @@ -33,6 +50,20 @@ func (c *UserStorageController) getJWTTokens(ctx context.Context, app model.AppD
f := model.FieldsetForScopes(scopes)
data := map[string]any{}

// if we need tenant data in id token
if needTenantInfo(scopes) {
// we can already have userData fetched for access token
if len(ud.UserID) == 0 {
ud, err = c.u.UserData(ctx, u.ID, model.UserDataFieldTenantMembership)
if err != nil {
return resp, err
}
}
ti := TenantData(ud.TenantMembership, scopes)
maps.Copy(data, ti)
}

// create id token
idt, err := c.ts.NewToken(model.TokenTypeID, u, f, data)
if err != nil {
return resp, err
Expand Down Expand Up @@ -79,14 +110,38 @@ func AccessTokenScopes(scopes []string) []string {
return result
}

func TenantData(ud model.UserData) map[string]any {
func TenantData(ud []model.TenantMembership, scopes []string) map[string]any {
res := map[string]any{}
for _, t := range ud.TenantMembership {
filter := []string{}
getAll := false
for _, s := range scopes {
if s == model.TenantScopeAll {
getAll = true
break
} else if strings.HasPrefix(s, model.TenantScopePrefix) && len(s) > len(model.TenantScopePrefix) {
filter = append(filter, s[len(model.TenantScopePrefix):])
}
}
for _, t := range ud {
// skip the scopes we don't need to have
if !getAll && !sliceContains(filter, t.TenantID) {
continue
}
tid := t.TenantID
res["tenant:"+t.TenantID] = t.TenantName
for k, v := range t.Groups {
// "tenant_id:group_id" : "role"
res[tid+":"+k] = v
}
}
return res
}

func needTenantInfo(scopes []string) bool {
for _, s := range scopes {
if strings.HasPrefix(s, model.TenantScopePrefix) {
return true
}
}
return false
}
Loading

0 comments on commit 250e19f

Please sign in to comment.