PR #10014 was not complete as only handled policy claims partially.
Unified · Split
| @@ -213,15 +213,15 @@ func getClaimsFromToken(r *http.Request, token string) (map[string]interface{}, · 212 unmodified lines | |||
| 213 | 213 | ||
| 214 | if globalPolicyOPA == nil { | 214 | if globalPolicyOPA == nil { |
| 215 | // If OPA is not set and if ldap claim key is set, allow the claim. | 215 | // If OPA is not set and if ldap claim key is set, allow the claim. |
| 216 | if _, ok := claims.Lookup(ldapUser); ok { | 216 | if _, ok := claims.MapClaims[ldapUser]; ok { |
| 217 | return claims.Map(), nil | 217 | return claims.Map(), nil |
| 218 | } | 218 | } |
| 219 | 219 | ||
| 220 | // If OPA is not set, session token should | 220 | // If OPA is not set, session token should |
| 221 | // have a policy and its mandatory, reject | 221 | // have a policy and its mandatory, reject |
| 222 | // requests without policy claim. | 222 | // requests without policy claim. |
| 223 | _, pokOpenID := claims.Lookup(iamPolicyClaimNameOpenID()) | 223 | _, pokOpenID := claims.MapClaims[iamPolicyClaimNameOpenID()] |
| 224 | _, pokSA := claims.Lookup(iamPolicyClaimNameSA()) | 224 | _, pokSA := claims.MapClaims[iamPolicyClaimNameSA()] |
| 225 | if !pokOpenID && !pokSA { | 225 | if !pokOpenID && !pokSA { |
| 226 | return nil, errAuthentication | 226 | return nil, errAuthentication |
| 227 | } | 227 | } |
| @@ -360,6 +360,7 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio · 132 unmodified lines | |||
| 360 | // Request is allowed return the appropriate access key. | 360 | // Request is allowed return the appropriate access key. |
| 361 | return cred.AccessKey, owner, ErrNone | 361 | return cred.AccessKey, owner, ErrNone |
| 362 | } | 362 | } |
| 363 | |||
| 363 | return cred.AccessKey, owner, ErrAccessDenied | 364 | return cred.AccessKey, owner, ErrAccessDenied |
| 364 | } | 365 | } |
| 365 | 366 | ||
| @@ -8,31 +8,31 @@ Configure and install keycloak server by following [Keycloak Installation Guide] · 7 unmodified lines | |||
| 8 | 8 | ||
| 9 | ### Configure Keycloak UI | 9 | ### Configure Keycloak UI |
| 10 | - Go to Clients | 10 | - Go to Clients |
| 11 | -> Click on account | 11 | - Click on account |
| 12 | -> Settings | 12 | - Settings |
| 13 | -> Enable `Implicit Flow` | 13 | - Enable `Implicit Flow` |
| 14 | -> Save | 14 | - Save |
| 15 | 15 | ||
| 16 | - Go to Users | 16 | - Go to Users |
| 17 | -> Click on the user | 17 | - Click on the user |
| 18 | -> Attribute, add a new attribute `Key` is `policy`, `Value` is name of the `policy` on MinIO (ex: `readwrite`) | 18 | - Attribute, add a new attribute `Key` is `policy`, `Value` is name of the `policy` on MinIO (ex: `readwrite`) |
| 19 | -> Add and Save | 19 | - Add and Save |
| 20 | 20 | ||
| 21 | - Go to Clients | 21 | - Go to Clients |
| 22 | -> Click on `account` | 22 | - Click on `account` |
| 23 | -> Settings, set `Valid Redirect URIs` to `*`, expand `Advanced Settings` and set `Access Token Lifespan` to `1 Hours` | 23 | - Settings, set `Valid Redirect URIs` to `*`, expand `Advanced Settings` and set `Access Token Lifespan` to `1 Hours` |
| 24 | -> Save | 24 | - Save |
| 25 | 25 | ||
| 26 | - Go to Clients | 26 | - Go to Clients |
| 27 | -> Client on `account` | 27 | - Client on `account` |
| 28 | -> Mappers | 28 | - Mappers |
| 29 | -> Create | 29 | - Create |
| 30 | - `Name` with any text | 30 | - `Name` with any text |
| 31 | - `Mapper Type` is `User Attribute` | 31 | - `Mapper Type` is `User Attribute` |
| 32 | - `User Attribute` is `policy` | 32 | - `User Attribute` is `policy` |
| 33 | - `Token Claim Name` is `policy` | 33 | - `Token Claim Name` is `policy` |
| 34 | - `Claim JSON Type` is `string` | 34 | - `Claim JSON Type` is `string` |
| 35 | -> Save | 35 | - Save |
| 36 | 36 | ||
| 37 | - Open http://localhost:8080/auth/realms/demo/.well-known/openid-configuration to verify OpenID discovery document, verify it has `authorization_endpoint` and `jwks_uri` | 37 | - Open http://localhost:8080/auth/realms/demo/.well-known/openid-configuration to verify OpenID discovery document, verify it has `authorization_endpoint` and `jwks_uri` |
| 38 | 38 | ||
| @@ -31,7 +31,9 @@ const ( · 30 unmodified lines | |||
| 31 | // JWTJti JWT unique identifier claim substitution. | 31 | // JWTJti JWT unique identifier claim substitution. |
| 32 | JWTJti Key = "jwt:jti" | 32 | JWTJti Key = "jwt:jti" |
| 33 | 33 | ||
| 34 | JWTUpn Key = "jwt:upn" | ||
| 34 | JWTName Key = "jwt:name" | 35 | JWTName Key = "jwt:name" |
| 36 | JWTGroups Key = "jwt:groups" | ||
| 35 | JWTGivenName Key = "jwt:given_name" | 37 | JWTGivenName Key = "jwt:given_name" |
| 36 | JWTFamilyName Key = "jwt:family_name" | 38 | JWTFamilyName Key = "jwt:family_name" |
| 37 | JWTMiddleName Key = "jwt:middle_name" | 39 | JWTMiddleName Key = "jwt:middle_name" |
| @@ -57,6 +59,8 @@ var JWTKeys = []Key{ · 19 unmodified lines | |||
| 57 | JWTAud, | 59 | JWTAud, |
| 58 | JWTJti, | 60 | JWTJti, |
| 59 | JWTName, | 61 | JWTName, |
| 62 | JWTUpn, | ||
| 63 | JWTGroups, | ||
| 60 | JWTGivenName, | 64 | JWTGivenName, |
| 61 | JWTFamilyName, | 65 | JWTFamilyName, |
| 62 | JWTMiddleName, | 66 | JWTMiddleName, |
| @@ -47,23 +47,36 @@ func GetPoliciesFromClaims(claims map[string]interface{}, policyClaimName string · 46 unmodified lines | |||
| 47 | if !ok { | 47 | if !ok { |
| 48 | return s, false | 48 | return s, false |
| 49 | } | 49 | } |
| 50 | pnames, ok := pname.([]string) | 50 | pnames, ok := pname.([]interface{}) |
| 51 | if !ok { | 51 | if !ok { |
| 52 | pnameStr, ok := pname.(string) | 52 | pnameStr, ok := pname.(string) |
| 53 | if ok { | 53 | if ok { |
| 54 | pnames = strings.Split(pnameStr, ",") | 54 | for _, pname := range strings.Split(pnameStr, ",") { |
| 55 | } else { | 55 | pname = strings.TrimSpace(pname) |
| 56 | return s, false | 56 | if pname == "" { |
| 57 | // ignore any empty strings, considerate | ||
| 58 | // towards some user errors. | ||
| 59 | continue | ||
| 60 | } | ||
| 61 | s.Add(pname) | ||
| 62 | } | ||
| 63 | return s, true | ||
| 57 | } | 64 | } |
| 65 | return s, false | ||
| 58 | } | 66 | } |
| 59 | for _, pname := range pnames { | 67 | for _, pname := range pnames { |
| 60 | pname = strings.TrimSpace(pname) | 68 | pnameStr, ok := pname.(string) |
| 61 | if pname == "" { | 69 | if ok { |
| 62 | // ignore any empty strings, considerate | 70 | for _, pnameStr := range strings.Split(pnameStr, ",") { |
| 63 | // towards some user errors. | 71 | pnameStr = strings.TrimSpace(pnameStr) |
| 64 | continue | 72 | if pnameStr == "" { |
| 73 | // ignore any empty strings, considerate | ||
| 74 | // towards some user errors. | ||
| 75 | continue | ||
| 76 | } | ||
| 77 | s.Add(pnameStr) | ||
| 78 | } | ||
| 65 | } | 79 | } |
| 66 | s.Add(pname) | ||
| 67 | } | 80 | } |
| 68 | return s, true | 81 | return s, true |
| 69 | } | 82 | } |
| @@ -22,10 +22,55 @@ import ( · 21 unmodified lines | |||
| 22 | "reflect" | 22 | "reflect" |
| 23 | "testing" | 23 | "testing" |
| 24 | 24 | ||
| 25 | "github.com/minio/minio-go/v6/pkg/set" | ||
| 25 | "github.com/minio/minio/pkg/bucket/policy" | 26 | "github.com/minio/minio/pkg/bucket/policy" |
| 26 | "github.com/minio/minio/pkg/bucket/policy/condition" | 27 | "github.com/minio/minio/pkg/bucket/policy/condition" |
| 27 | ) | 28 | ) |
| 28 | 29 | ||
| 30 | func TestGetPoliciesFromClaims(t *testing.T) { | ||
| 31 | attributesArray := `{ | ||
| 32 | "exp": 1594690452, | ||
| 33 | "iat": 1594689552, | ||
| 34 | "auth_time": 1594689552, | ||
| 35 | "jti": "18ed05c9-2c69-45d5-a33f-8c94aca99ad5", | ||
| 36 | "iss": "http://localhost:8080/auth/realms/minio", | ||
| 37 | "aud": "account", | ||
| 38 | "sub": "7e5e2f30-1c97-4616-8623-2eae14dee9b1", | ||
| 39 | "typ": "ID", | ||
| 40 | "azp": "account", | ||
| 41 | "nonce": "66ZoLzwJbjdkiedI", | ||
| 42 | "session_state": "3df7b526-5310-4038-9f35-50ecd295a31d", | ||
| 43 | "acr": "1", | ||
| 44 | "upn": "harsha", | ||
| 45 | "address": {}, | ||
| 46 | "email_verified": false, | ||
| 47 | "groups": [ | ||
| 48 | "offline_access" | ||
| 49 | ], | ||
| 50 | "preferred_username": "harsha", | ||
| 51 | "policy": [ | ||
| 52 | "readwrite", | ||
| 53 | "readwrite,readonly", | ||
| 54 | " readonly", | ||
| 55 | "" | ||
| 56 | ]}` | ||
| 57 | var m = make(map[string]interface{}) | ||
| 58 | if err := json.Unmarshal([]byte(attributesArray), &m); err != nil { | ||
| 59 | t.Fatal(err) | ||
| 60 | } | ||
| 61 | var expectedSet = set.CreateStringSet("readwrite", "readonly") | ||
| 62 | gotSet, ok := GetPoliciesFromClaims(m, "policy") | ||
| 63 | if !ok { | ||
| 64 | t.Fatal("no policy claim was found") | ||
| 65 | } | ||
| 66 | if gotSet.IsEmpty() { | ||
| 67 | t.Fatal("no policies were found in policy claim") | ||
| 68 | } | ||
| 69 | if !gotSet.Equals(expectedSet) { | ||
| 70 | t.Fatalf("Expected %v got %v", expectedSet, gotSet) | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 29 | func TestPolicyIsAllowed(t *testing.T) { | 74 | func TestPolicyIsAllowed(t *testing.T) { |
| 30 | case1Policy := Policy{ | 75 | case1Policy := Policy{ |
| 31 | Version: DefaultVersion, | 76 | Version: DefaultVersion, |