Deny suspended directory identities
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 24s
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 24s
This commit is contained in:
parent
76da0237ff
commit
909bb327fc
4 changed files with 67 additions and 7 deletions
|
|
@ -108,6 +108,14 @@ func (h *TokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, "user not found", http.StatusInternalServerError)
|
http.Error(w, "user not found", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if isSuspended(user) {
|
||||||
|
h.Sessions.Delete(code)
|
||||||
|
profileerrors.RejectedForSafety(
|
||||||
|
"account is suspended",
|
||||||
|
"account_lifecycle",
|
||||||
|
).Write(w, http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 6. Build JWT claims.
|
// 6. Build JWT claims.
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
@ -270,6 +278,10 @@ func containsString(values []string, wanted string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSuspended(user *domain.User) bool {
|
||||||
|
return containsString(user.Groups, "netkingdom-suspended")
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// IAM Profile core claims (KEY-WP-0005-T01)
|
// IAM Profile core claims (KEY-WP-0005-T01)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,30 @@ func TestTokenHandler_ValidExchange_ReturnsJWT(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTokenHandler_SuspendedUserCannotExchangeCode(t *testing.T) {
|
||||||
|
sessions := oidc.NewSessionStore()
|
||||||
|
user := aliceUser()
|
||||||
|
user.Groups = append(user.Groups, "netkingdom-suspended")
|
||||||
|
users := &mockUserRepo{users: map[string]*domain.User{"alice": user}}
|
||||||
|
h, _ := newTokenHandler(t, sessions, users)
|
||||||
|
verifier := "suspended-verifier-with-enough-entropy"
|
||||||
|
code := seededSession(sessions, verifier)
|
||||||
|
req := tokenRequest(url.Values{
|
||||||
|
"grant_type": {"authorization_code"},
|
||||||
|
"code": {code},
|
||||||
|
"client_id": {"test-client"},
|
||||||
|
"code_verifier": {verifier},
|
||||||
|
})
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
h.ServeHTTP(w, req)
|
||||||
|
if w.Code != http.StatusForbidden {
|
||||||
|
t.Fatalf("expected 403, got %d: %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
if _, ok := sessions.Get(code); ok {
|
||||||
|
t.Fatal("authorization code must be consumed when suspension is detected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTokenHandler_WrongGrantType_FeatureNotSupported(t *testing.T) {
|
func TestTokenHandler_WrongGrantType_FeatureNotSupported(t *testing.T) {
|
||||||
sessions := oidc.NewSessionStore()
|
sessions := oidc.NewSessionStore()
|
||||||
users := &mockUserRepo{}
|
users := &mockUserRepo{}
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ import (
|
||||||
// the user, and returns claims that are consistent with those in the ID token
|
// the user, and returns claims that are consistent with those in the ID token
|
||||||
// for the same scope set.
|
// for the same scope set.
|
||||||
type UserinfoHandler struct {
|
type UserinfoHandler struct {
|
||||||
Users domain.UserRepository
|
Users domain.UserRepository
|
||||||
SigningKey *rsa.PublicKey // used to verify the incoming access token
|
SigningKey *rsa.PublicKey // used to verify the incoming access token
|
||||||
Issuer string
|
Issuer string
|
||||||
Emitter telemetry.Emitter
|
Emitter telemetry.Emitter
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP handles GET /userinfo.
|
// ServeHTTP handles GET /userinfo.
|
||||||
|
|
@ -59,6 +59,10 @@ func (h *UserinfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, `{"error":"invalid_token","description":"subject not found"}`, http.StatusUnauthorized)
|
http.Error(w, `{"error":"invalid_token","description":"subject not found"}`, http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if isSuspended(user) {
|
||||||
|
http.Error(w, `{"error":"invalid_token","description":"subject is suspended"}`, http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 5. Build response claims filtered by the scopes embedded in the token.
|
// 5. Build response claims filtered by the scopes embedded in the token.
|
||||||
scopeStr, _ := claims["scope"].(string)
|
scopeStr, _ := claims["scope"].(string)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ func newUserinfoHandler(t *testing.T, users domain.UserRepository) (*oidc.Userin
|
||||||
capture := &captureEmitter{}
|
capture := &captureEmitter{}
|
||||||
h := &oidc.UserinfoHandler{
|
h := &oidc.UserinfoHandler{
|
||||||
Users: users,
|
Users: users,
|
||||||
SigningKey: &key.PublicKey,
|
SigningKey: &key.PublicKey,
|
||||||
Issuer: "https://auth.netkingdom.local",
|
Issuer: "https://auth.netkingdom.local",
|
||||||
Emitter: capture,
|
Emitter: capture,
|
||||||
}
|
}
|
||||||
|
|
@ -100,6 +100,26 @@ func TestUserinfoHandler_ValidToken_ReturnsClaims(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserinfoHandler_SuspendedUserInvalidatesToken(t *testing.T) {
|
||||||
|
user := aliceUser()
|
||||||
|
user.Groups = append(user.Groups, "netkingdom-suspended")
|
||||||
|
users := &mockUserRepo{users: map[string]*domain.User{"alice": user}}
|
||||||
|
h, key := newUserinfoHandler(t, users)
|
||||||
|
now := time.Now()
|
||||||
|
token := buildToken(t, map[string]interface{}{
|
||||||
|
"iss": "https://auth.netkingdom.local",
|
||||||
|
"sub": "alice",
|
||||||
|
"aud": "test-client",
|
||||||
|
"exp": now.Add(10 * time.Minute).Unix(),
|
||||||
|
"iat": now.Unix(),
|
||||||
|
}, key)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
h.ServeHTTP(w, userinfoRequest(token))
|
||||||
|
if w.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("expected 401, got %d: %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUserinfoHandler_MissingAuthorization_Returns401(t *testing.T) {
|
func TestUserinfoHandler_MissingAuthorization_Returns401(t *testing.T) {
|
||||||
users := &mockUserRepo{}
|
users := &mockUserRepo{}
|
||||||
h, _ := newUserinfoHandler(t, users)
|
h, _ := newUserinfoHandler(t, users)
|
||||||
|
|
@ -246,10 +266,10 @@ func TestUserinfoHandler_EmitsTelemetry(t *testing.T) {
|
||||||
key, _ := rsa.GenerateKey(rand.Reader, 2048)
|
key, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
capture := &captureEmitter{}
|
capture := &captureEmitter{}
|
||||||
h := &oidc.UserinfoHandler{
|
h := &oidc.UserinfoHandler{
|
||||||
Users: users,
|
Users: users,
|
||||||
SigningKey: &key.PublicKey,
|
SigningKey: &key.PublicKey,
|
||||||
Issuer: "https://auth.netkingdom.local",
|
Issuer: "https://auth.netkingdom.local",
|
||||||
Emitter: capture,
|
Emitter: capture,
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue