Skip to content

Commit

Permalink
Add roles and roles claim for users
Browse files Browse the repository at this point in the history
  • Loading branch information
giftkugel committed Aug 28, 2024
1 parent be875ad commit d54d551
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 25 deletions.
18 changes: 14 additions & 4 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,11 @@ type UserProfile struct {
}

type User struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
Salt string `yaml:"salt"`
Profile UserProfile `yaml:"profile"`
Username string `yaml:"username"`
Password string `yaml:"password"`
Salt string `yaml:"salt"`
Profile UserProfile `yaml:"profile"`
Roles map[string][]string `yaml:"roles"`
}

type Claim struct {
Expand All @@ -99,6 +100,7 @@ type Client struct {
Issuer string `yaml:"issuer"`
Audience []string `yaml:"audience"`
PrivateKey string `yaml:"privateKey"`
RolesClaim string `yaml:"rolesClaim"`
}

type UI struct {
Expand Down Expand Up @@ -307,6 +309,10 @@ func (config *Config) GetOidc() bool {
return config.oidc
}

func (client *Client) GetRolesClaim() string {
return GetOrDefaultString(client.RolesClaim, "roles")
}

func (client *Client) GetAccessTTL() int {
return GetOrDefaultInt(client.AccessTTL, 5)
}
Expand Down Expand Up @@ -355,3 +361,7 @@ func (user *User) GetFormattedAddress() string {
}
return sb.String()
}

func (user *User) GetRoles(clientId string) []string {
return user.Roles[clientId]
}
26 changes: 19 additions & 7 deletions internal/manager/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/webishdev/stopnik/internal/crypto"
internalHttp "github.com/webishdev/stopnik/internal/http"
"github.com/webishdev/stopnik/internal/oauth2"
"github.com/webishdev/stopnik/internal/oidc"
"github.com/webishdev/stopnik/internal/store"
"github.com/webishdev/stopnik/log"
"net/http"
Expand Down Expand Up @@ -106,26 +107,33 @@ func (tokenManager *TokenManager) CreateAccessTokenResponse(r *http.Request, use
return accessTokenResponse
}

func (tokenManager *TokenManager) ValidateAccessToken(authorizationHeader string) (*config.User, []string, bool) {
func (tokenManager *TokenManager) ValidateAccessToken(authorizationHeader string) (*config.User, *config.Client, []string, bool) {
log.Debug("Validating access token")
accessTokenStore := *tokenManager.accessTokenStore
headerValue := getAuthorizationHeaderValue(authorizationHeader)
if headerValue == nil {
return nil, []string{}, false
return nil, nil, []string{}, false
}
accessToken, authorizationHeaderExists := accessTokenStore.Get(*headerValue)
if !authorizationHeaderExists {
return nil, []string{}, false
return nil, nil, []string{}, false
}

username := accessToken.Username
user, userExists := tokenManager.config.GetUser(username)

if !userExists {
return nil, []string{}, false
return nil, nil, []string{}, false
}

return user, accessToken.Scopes, true
clientId := accessToken.ClientId
client, clientExists := tokenManager.config.GetClient(clientId)

if !clientExists {
return nil, nil, []string{}, false
}

return user, client, accessToken.Scopes, true
}

func (tokenManager *TokenManager) generateIdToken(requestData *internalHttp.RequestData, user *config.User, client *config.Client, nonce string, duration time.Duration) string {
Expand Down Expand Up @@ -208,8 +216,12 @@ func generateIdToken(requestData *internalHttp.RequestData, user *config.User, c
audience := append(client.GetAudience(), client.Id)
builder.Audience(audience)

builder.Claim("azp", client.Id)
builder.Claim("nonce", nonce)
builder.Claim(oidc.ClaimAuthorizedParty, client.Id)
builder.Claim(oidc.ClaimNonce, nonce)
roles := user.GetRoles(client.Id)
if len(roles) != 0 {
builder.Claim(client.GetRolesClaim(), roles)
}

token, builderError := builder.Build()

Expand Down
10 changes: 5 additions & 5 deletions internal/manager/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ func Test_Token(t *testing.T) {

authorizationHeader := fmt.Sprintf("%s %s", internalHttp.AuthBearer, accessTokenResponse.AccessTokenKey)

user, scopes, userExists := tokenManager.ValidateAccessToken(authorizationHeader)
if !userExists {
user, _, scopes, valid := tokenManager.ValidateAccessToken(authorizationHeader)
if !valid {
t.Error("user does not exist")
}

Expand Down Expand Up @@ -113,7 +113,7 @@ func Test_Token(t *testing.T) {
keyManager := createTestKeyManager(t, testConfig)
tokenManager := NewTokenManager(testConfig, NewDefaultKeyLoader(testConfig, keyManager))

_, _, valid := tokenManager.ValidateAccessToken("foooo")
_, _, _, valid := tokenManager.ValidateAccessToken("foooo")

if valid {
t.Error("should not be valid")
Expand All @@ -125,7 +125,7 @@ func Test_Token(t *testing.T) {
keyManager := createTestKeyManager(t, testConfig)
tokenManager := NewTokenManager(testConfig, NewDefaultKeyLoader(testConfig, keyManager))

_, _, valid := tokenManager.ValidateAccessToken(fmt.Sprintf("%s %s", internalHttp.AuthBearer, "foo"))
_, _, _, valid := tokenManager.ValidateAccessToken(fmt.Sprintf("%s %s", internalHttp.AuthBearer, "foo"))

if valid {
t.Error("should not be valid")
Expand All @@ -144,7 +144,7 @@ func Test_Token(t *testing.T) {
request := httptest.NewRequest(http.MethodPost, endpoint.Token, nil)
accessTokenResponse := tokenManager.CreateAccessTokenResponse(request, "bar", client, []string{"abc", "def"}, "")

_, _, valid := tokenManager.ValidateAccessToken(fmt.Sprintf("%s %s", internalHttp.AuthBearer, accessTokenResponse.AccessTokenKey))
_, _, _, valid := tokenManager.ValidateAccessToken(fmt.Sprintf("%s %s", internalHttp.AuthBearer, accessTokenResponse.AccessTokenKey))

if valid {
t.Error("should not be valid")
Expand Down
6 changes: 6 additions & 0 deletions internal/oidc/claims.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package oidc

const (
ClaimNonce string = "nonce"
ClaimAuthorizedParty string = "azp"
)
4 changes: 2 additions & 2 deletions internal/server/handler/health/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
healthResponse := Health{Ping: "pong"}

authorizationHeader := r.Header.Get(internalHttp.Authorization)
user, scopes, userExists := h.tokenManager.ValidateAccessToken(authorizationHeader)
if userExists {
user, _, scopes, valid := h.tokenManager.ValidateAccessToken(authorizationHeader)
if valid {
healthResponse.Username = user.Username
healthResponse.Scopes = scopes
}
Expand Down
4 changes: 2 additions & 2 deletions internal/server/handler/introspect/introspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

// Fall back to access token with scopes
authorizationHeader := r.Header.Get(internalHttp.Authorization)
_, scopes, userExists := h.tokenManager.ValidateAccessToken(authorizationHeader)
if !userExists {
_, _, scopes, valid := h.tokenManager.ValidateAccessToken(authorizationHeader)
if !valid {
oauth2.TokenErrorStatusResponseHandler(w, http.StatusUnauthorized, &oauth2.TokenErrorResponseParameter{Error: oauth2.TokenEtInvalidRequest})
return
}
Expand Down
36 changes: 33 additions & 3 deletions internal/server/handler/oidc/userinfo.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package oidc

import (
"encoding/json"
"github.com/webishdev/stopnik/internal/config"
internalHttp "github.com/webishdev/stopnik/internal/http"
"github.com/webishdev/stopnik/internal/manager"
Expand All @@ -26,17 +27,29 @@ func (h *UserInfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
var userInfoResponse *config.UserProfile
authorizationHeader := r.Header.Get(internalHttp.Authorization)
user, _, userExists := h.tokenManager.ValidateAccessToken(authorizationHeader)
if userExists {
user, client, _, valid := h.tokenManager.ValidateAccessToken(authorizationHeader)
if valid {
userInfoResponse = &user.Profile
userInfoResponse.Subject = user.Username
userInfoResponse.PreferredUserName = user.Username
userInfoResponse.Name = userInfoResponse.GivenName + " " + userInfoResponse.FamilyName
userInfoResponse.Address.Formatted = user.GetFormattedAddress()
} else {
userInfoResponse = &config.UserProfile{}
}

jsonError := internalHttp.SendJson(userInfoResponse, w)
var result interface{}
result = userInfoResponse

roles := user.GetRoles(client.Id)
if len(roles) != 0 {
updateResult, updateError := updateRoles(userInfoResponse, client.GetRolesClaim(), roles)
if updateError == nil {
result = updateResult
}
}

jsonError := internalHttp.SendJson(result, w)
if jsonError != nil {
h.errorHandler.InternalServerErrorHandler(w, r)
return
Expand All @@ -46,3 +59,20 @@ func (h *UserInfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
}

func updateRoles(userProfile *config.UserProfile, rolesName string, roles []string) (map[string]interface{}, error) {
marshaledUserProfile, marshalError := json.Marshal(userProfile)
if marshalError != nil {
return nil, marshalError
}

var a map[string]interface{}
unmarshalError := json.Unmarshal(marshaledUserProfile, &a)
if unmarshalError != nil {
return nil, unmarshalError
}

a[rolesName] = roles

return a, nil
}
4 changes: 2 additions & 2 deletions internal/server/handler/revoke/revoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

// Fall back to access token with scopes
authorizationHeader := r.Header.Get(internalHttp.Authorization)
_, scopes, userExists := h.tokenManager.ValidateAccessToken(authorizationHeader)
if !userExists {
_, _, scopes, valid := h.tokenManager.ValidateAccessToken(authorizationHeader)
if !valid {
oauth2.TokenErrorStatusResponseHandler(w, http.StatusUnauthorized, &oauth2.TokenErrorResponseParameter{Error: oauth2.TokenEtInvalidRequest})
return
}
Expand Down

0 comments on commit d54d551

Please sign in to comment.