Skip to content

Commit f08754e

Browse files
authored
Added APIs to support fine-grained authorization (Nerzal#432)
* Added APIs to support fine-grained authorization * fixed linting error * Added test cases Enabled admin-fine-grained-authz feature Replaced sleep 10 with loop to wait for health endpoint availability * Added test cases Enabled admin-fine-grained-authz feature Replaced sleep 10 with loop to wait for health endpoint availability
1 parent 166f442 commit f08754e

File tree

5 files changed

+252
-3
lines changed

5 files changed

+252
-3
lines changed

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ ENV KEYCLOAK_USER=admin
66
ENV KEYCLOAK_PASSWORD=secret
77
ENV KEYCLOAK_ADMIN=admin
88
ENV KEYCLOAK_ADMIN_PASSWORD=secret
9-
ENV KC_FEATURES=account-api,account2,authorization,client-policies,impersonation,docker,scripts,upload_scripts
9+
ENV KC_FEATURES=account-api,account2,authorization,client-policies,impersonation,docker,scripts,upload_scripts,admin-fine-grained-authz
1010
RUN /opt/keycloak/bin/kc.sh import --file /data/import/gocloak-realm.json
1111
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]

client.go

+98
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,24 @@ func (g *GoCloak) UpdateGroup(ctx context.Context, token, realm string, updatedG
869869
return checkForError(resp, err, errMessage)
870870
}
871871

872+
// UpdateGroupManagementPermissions updates the given group management permissions
873+
func (g *GoCloak) UpdateGroupManagementPermissions(ctx context.Context, accessToken, realm string, idOfGroup string, managementPermissions ManagementPermissionRepresentation) (*ManagementPermissionRepresentation, error) {
874+
const errMessage = "could not update group management permissions"
875+
876+
var result ManagementPermissionRepresentation
877+
878+
resp, err := g.GetRequestWithBearerAuth(ctx, accessToken).
879+
SetResult(&result).
880+
SetBody(managementPermissions).
881+
Put(g.getAdminRealmURL(realm, "groups", idOfGroup, "management", "permissions"))
882+
883+
if err := checkForError(resp, err, errMessage); err != nil {
884+
return nil, err
885+
}
886+
887+
return &result, nil
888+
}
889+
872890
// UpdateClient updates the given Client
873891
func (g *GoCloak) UpdateClient(ctx context.Context, token, realm string, updatedClient Client) error {
874892
const errMessage = "could not update client"
@@ -906,6 +924,24 @@ func (g *GoCloak) UpdateClientRepresentation(ctx context.Context, accessToken, r
906924
return &result, nil
907925
}
908926

927+
// UpdateClientManagementPermissions updates the given client management permissions
928+
func (g *GoCloak) UpdateClientManagementPermissions(ctx context.Context, accessToken, realm string, idOfClient string, managementPermissions ManagementPermissionRepresentation) (*ManagementPermissionRepresentation, error) {
929+
const errMessage = "could not update client management permissions"
930+
931+
var result ManagementPermissionRepresentation
932+
933+
resp, err := g.GetRequestWithBearerAuth(ctx, accessToken).
934+
SetResult(&result).
935+
SetBody(managementPermissions).
936+
Put(g.getAdminRealmURL(realm, "clients", idOfClient, "management", "permissions"))
937+
938+
if err := checkForError(resp, err, errMessage); err != nil {
939+
return nil, err
940+
}
941+
942+
return &result, nil
943+
}
944+
909945
// UpdateRole updates the given role.
910946
func (g *GoCloak) UpdateRole(ctx context.Context, token, realm, idOfClient string, role Role) error {
911947
const errMessage = "could not update role"
@@ -1682,6 +1718,23 @@ func (g *GoCloak) GetGroups(ctx context.Context, token, realm string, params Get
16821718
return result, nil
16831719
}
16841720

1721+
// GetGroupManagementPermissions returns whether group Authorization permissions have been initialized or not and a reference
1722+
// to the managed permissions
1723+
func (g *GoCloak) GetGroupManagementPermissions(ctx context.Context, token, realm string, idOfGroup string) (*ManagementPermissionRepresentation, error) {
1724+
const errMessage = "could not get management permissions"
1725+
1726+
var result ManagementPermissionRepresentation
1727+
resp, err := g.GetRequestWithBearerAuth(ctx, token).
1728+
SetResult(&result).
1729+
Get(g.getAdminRealmURL(realm, "groups", idOfGroup, "management", "permissions"))
1730+
1731+
if err := checkForError(resp, err, errMessage); err != nil {
1732+
return nil, err
1733+
}
1734+
1735+
return &result, nil
1736+
}
1737+
16851738
// GetGroupsByRole gets groups assigned with a specific role of a realm
16861739
func (g *GoCloak) GetGroupsByRole(ctx context.Context, token, realm string, roleName string) ([]*Group, error) {
16871740
const errMessage = "could not get groups"
@@ -1944,6 +1997,23 @@ func (g *GoCloak) GetClients(ctx context.Context, token, realm string, params Ge
19441997
return result, nil
19451998
}
19461999

2000+
// GetClientManagementPermissions returns whether client Authorization permissions have been initialized or not and a reference
2001+
// to the managed permissions
2002+
func (g *GoCloak) GetClientManagementPermissions(ctx context.Context, token, realm string, idOfClient string) (*ManagementPermissionRepresentation, error) {
2003+
const errMessage = "could not get management permissions"
2004+
2005+
var result ManagementPermissionRepresentation
2006+
resp, err := g.GetRequestWithBearerAuth(ctx, token).
2007+
SetResult(&result).
2008+
Get(g.getAdminRealmURL(realm, "clients", idOfClient, "management", "permissions"))
2009+
2010+
if err := checkForError(resp, err, errMessage); err != nil {
2011+
return nil, err
2012+
}
2013+
2014+
return &result, nil
2015+
}
2016+
19472017
// UserAttributeContains checks if the given attribute value is set
19482018
func UserAttributeContains(attributes map[string][]string, attribute, value string) bool {
19492019
for _, item := range attributes[attribute] {
@@ -3316,6 +3386,34 @@ func (g *GoCloak) CreateScope(ctx context.Context, token, realm, idOfClient stri
33163386
return &result, nil
33173387
}
33183388

3389+
// GetPermissionScope gets the permission scope associated with the client
3390+
func (g *GoCloak) GetPermissionScope(ctx context.Context, token, realm, idOfClient string, idOfScope string) (*PolicyRepresentation, error) {
3391+
const errMessage = "could not get permission scope"
3392+
3393+
var result PolicyRepresentation
3394+
resp, err := g.GetRequestWithBearerAuth(ctx, token).
3395+
SetResult(&result).
3396+
SetBody(result).
3397+
Get(g.getAdminRealmURL(realm, "clients", idOfClient, "authz", "resource-server", "permission", "scope", idOfScope))
3398+
3399+
if err := checkForError(resp, err, errMessage); err != nil {
3400+
return nil, err
3401+
}
3402+
3403+
return &result, nil
3404+
}
3405+
3406+
// UpdatePermissionScope updates a permission scope associated with the client
3407+
func (g *GoCloak) UpdatePermissionScope(ctx context.Context, token, realm, idOfClient string, idOfScope string, policy PolicyRepresentation) error {
3408+
const errMessage = "could not create permission scope"
3409+
3410+
resp, err := g.GetRequestWithBearerAuth(ctx, token).
3411+
SetBody(policy).
3412+
Put(g.getAdminRealmURL(realm, "clients", idOfClient, "authz", "resource-server", "permission", "scope", idOfScope))
3413+
3414+
return checkForError(resp, err, errMessage)
3415+
}
3416+
33193417
// UpdateScope updates a scope associated with the client
33203418
func (g *GoCloak) UpdateScope(ctx context.Context, token, realm, idOfClient string, scope ScopeRepresentation) error {
33213419
const errMessage = "could not update scope"

client_test.go

+133
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,103 @@ func Test_CreateListGetUpdateDeleteGetChildGroup(t *testing.T) {
11961196
require.NoError(t, err, "GetGroup failed")
11971197
}
11981198

1199+
func Test_GroupPermissions(t *testing.T) {
1200+
cfg := GetConfig(t)
1201+
client := NewClientWithDebug(t)
1202+
token := GetAdminToken(t, client)
1203+
1204+
// Create
1205+
tearDown, groupID := CreateGroup(t, client)
1206+
// Delete
1207+
defer tearDown()
1208+
1209+
groupPermission, err := client.GetGroupManagementPermissions(
1210+
context.Background(),
1211+
token.AccessToken,
1212+
cfg.GoCloak.Realm,
1213+
groupID,
1214+
)
1215+
require.NoError(t, err, "GetGroupManagementPermissions failed")
1216+
require.Equal(t, false, *groupPermission.Enabled)
1217+
1218+
groupPermission.Enabled = gocloak.BoolP(true)
1219+
updatedGroupPermission, err := client.UpdateGroupManagementPermissions(
1220+
context.Background(),
1221+
token.AccessToken,
1222+
cfg.GoCloak.Realm,
1223+
groupID,
1224+
*groupPermission,
1225+
)
1226+
require.NoError(t, err, "UpdateGroupManagementPermissions failed")
1227+
require.Equal(t, true, *updatedGroupPermission.Enabled)
1228+
1229+
clients, err := client.GetClients(
1230+
context.Background(),
1231+
token.AccessToken,
1232+
cfg.GoCloak.Realm,
1233+
gocloak.GetClientsParams{
1234+
ClientID: gocloak.StringP("realm-management"),
1235+
},
1236+
)
1237+
require.NoError(t, err, "GetClients failed")
1238+
require.Equal(t, 1, len(clients))
1239+
realManagementClient := clients[0]
1240+
1241+
_, policyID := CreatePolicy(t, client, gocloakClientID, gocloak.PolicyRepresentation{
1242+
Name: GetRandomNameP("PolicyName"),
1243+
Description: gocloak.StringP("Policy Description"),
1244+
Type: gocloak.StringP("client"),
1245+
Logic: gocloak.POSITIVE,
1246+
ClientPolicyRepresentation: gocloak.ClientPolicyRepresentation{
1247+
Clients: &[]string{
1248+
gocloakClientID,
1249+
},
1250+
},
1251+
})
1252+
1253+
for _, scopeID := range *updatedGroupPermission.ScopePermissions {
1254+
permissionScope, err := client.GetPermissionScope(
1255+
context.Background(),
1256+
token.AccessToken,
1257+
cfg.GoCloak.Realm,
1258+
*realManagementClient.ID,
1259+
scopeID)
1260+
require.NoError(t, err, "GetPermissionScope failed for %s", scopeID)
1261+
1262+
scopePolicies, err := client.GetAuthorizationPolicyScopes(
1263+
context.Background(),
1264+
token.AccessToken,
1265+
cfg.GoCloak.Realm,
1266+
*realManagementClient.ID,
1267+
scopeID)
1268+
require.NoError(t, err, "GetAuthorizationPolicyScopes failed for %s", scopeID)
1269+
require.Equal(t, 1, len(scopePolicies), "GetAuthorizationPolicyScopes found more than 1 policies")
1270+
scopePolicy := scopePolicies[0]
1271+
1272+
policyResources, err := client.GetAuthorizationPolicyResources(
1273+
context.Background(),
1274+
token.AccessToken,
1275+
cfg.GoCloak.Realm,
1276+
*realManagementClient.ID,
1277+
scopeID)
1278+
require.NoError(t, err, "GetAuthorizationPolicyResources failed for %s", scopeID)
1279+
require.Equal(t, 1, len(policyResources), "GetAuthorizationPolicyResources found more than 1 policies")
1280+
policyResource := policyResources[0]
1281+
1282+
permissionScope.Policies = &[]string{policyID}
1283+
permissionScope.Resources = &[]string{*policyResource.ID}
1284+
permissionScope.Scopes = &[]string{*scopePolicy.ID}
1285+
err = client.UpdatePermissionScope(
1286+
context.Background(),
1287+
token.AccessToken,
1288+
cfg.GoCloak.Realm,
1289+
*realManagementClient.ID,
1290+
scopeID,
1291+
*permissionScope)
1292+
require.NoError(t, err, "UpdatePermissionScope failed for %s", scopeID)
1293+
}
1294+
}
1295+
11991296
func CreateClientRole(t *testing.T, client *gocloak.GoCloak) (func(), string) {
12001297
cfg := GetConfig(t)
12011298
token := GetAdminToken(t, client)
@@ -1226,6 +1323,42 @@ func CreateClientRole(t *testing.T, client *gocloak.GoCloak) (func(), string) {
12261323
return tearDown, roleName
12271324
}
12281325

1326+
func Test_ClientPermissions(t *testing.T) {
1327+
cfg := GetConfig(t)
1328+
client := NewClientWithDebug(t)
1329+
token := GetAdminToken(t, client)
1330+
1331+
t.Logf("Checking Client Permission")
1332+
testClient := gocloak.Client{
1333+
ClientID: GetRandomNameP("ClientID"),
1334+
BaseURL: gocloak.StringP("https://example.com"),
1335+
FullScopeAllowed: gocloak.BoolP(false),
1336+
}
1337+
// Creating client
1338+
tearDownClient, idOfClient := CreateClient(t, client, &testClient)
1339+
defer tearDownClient()
1340+
1341+
clientPermissions, err := client.GetClientManagementPermissions(
1342+
context.Background(),
1343+
token.AccessToken,
1344+
cfg.GoCloak.Realm,
1345+
idOfClient,
1346+
)
1347+
require.NoError(t, err, "GetClientManagementPermissions failed")
1348+
require.Equal(t, false, *clientPermissions.Enabled)
1349+
1350+
clientPermissions.Enabled = gocloak.BoolP(true)
1351+
updatedClientPermissions, err := client.UpdateClientManagementPermissions(
1352+
context.Background(),
1353+
token.AccessToken,
1354+
cfg.GoCloak.Realm,
1355+
idOfClient,
1356+
*clientPermissions,
1357+
)
1358+
require.NoError(t, err, "UpdateClientManagementPermissions failed")
1359+
require.Equal(t, true, *updatedClientPermissions.Enabled)
1360+
}
1361+
12291362
func Test_CreateClientRole(t *testing.T) {
12301363
t.Parallel()
12311364
client := NewClientWithDebug(t)

models.go

+8
Original file line numberDiff line numberDiff line change
@@ -1408,6 +1408,14 @@ type RequiredActionProviderRepresentation struct {
14081408
ProviderID *string `json:"providerId,omitempty"`
14091409
}
14101410

1411+
// ManagementPermissionRepresentation is a representation of management permissions
1412+
// v18: https://www.keycloak.org/docs-api/18.0/rest-api/#_managementpermissionreference
1413+
type ManagementPermissionRepresentation struct {
1414+
Enabled *bool `json:"enabled,omitempty"`
1415+
Resource *string `json:"resource,omitempty"`
1416+
ScopePermissions *map[string]string `json:"scopePermissions,omitempty"`
1417+
}
1418+
14111419
// prettyStringStruct returns struct formatted into pretty string
14121420
func prettyStringStruct(t interface{}) string {
14131421
json, err := json.MarshalIndent(t, "", "\t")

run-tests.sh

+12-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,17 @@
33
docker-compose down
44
docker-compose up -d
55

6-
sleep 10
6+
keycloakServer=http://localhost:8080
7+
url="${keycloakServer}/health"
8+
echo "Checking service availability at $url (CTRL+C to exit)"
9+
while true; do
10+
response=$(curl -s -o /dev/null -w "%{http_code}" $url)
11+
if [ $response -eq 200 ]; then
12+
break
13+
fi
14+
sleep 1
15+
done
16+
echo "Service is now available at ${keycloakServer}"
717

818
ARGS=()
919
if [ $# -gt 0 ]; then
@@ -13,4 +23,4 @@ fi
1323

1424
go test -failfast -race -cover -coverprofile=coverage.out -covermode=atomic -p 10 -cpu 1,2 -bench . -benchmem ${ARGS[@]}
1525

16-
docker-compose down
26+
docker-compose down

0 commit comments

Comments
 (0)