Skip to content

Commit

Permalink
Add keycloak_role_permissions for admin_fine_grained_authhz
Browse files Browse the repository at this point in the history
  • Loading branch information
jermarchand committed Oct 30, 2020
1 parent 898094d commit 8967ec2
Show file tree
Hide file tree
Showing 5 changed files with 568 additions and 0 deletions.
91 changes: 91 additions & 0 deletions docs/resources/roles_permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
page_title: "keycloak_role_permissions Resource"
---

# keycloak_role_permissions

Allows you to manage all role Scope Based Permissions https://www.keycloak.org/docs/latest/server_admin/#role.

This is part of a preview keycloak feature. You need to enable this feature to be able to use this resource.
More information about enabling the preview feature can be found here: https://www.keycloak.org/docs/latest/server_installation/#profiles

When enabling Users Permissions, Keycloak does several things automatically:
1. Enable Authorization on build-in realm-management client (if not already enabled)
1. Create a resource representing the role permissions
1. Create scopes "map-role", "map-role-client-scope", "map-role-composite"
1. Create all scope based permission for the scopes and role resource

If the realm-management Authorization is not enable, you have to ceate a dependency (`depends_on`) with the policy and the role.

### Example Usage

```hcl
resource "keycloak_realm" "realm" {
realm = "realm"
}
data keycloak_openid_client "realm_management" {
realm_id = keycloak_realm.realm.id
client_id = "realm-management"
}
resource keycloak_openid_client_permissions "realm-management_permission" {
realm_id = keycloak_realm.realm.id
client_id = data.keycloak_openid_client.realm_management.id
enabled = true
}
resource keycloak_user test {
realm_id = keycloak_realm.realm.id
username = "test-user"
email = "test-user@fakedomain.com"
first_name = "Testy"
last_name = "Tester"
}
resource keycloak_openid_client_user_policy test {
resource_server_id = "${data.keycloak_openid_client.realm_management.id}"
realm_id = keycloak_realm.realm.id
name = "client_user_policy_test"
users = ["${keycloak_user.test.id}"]
logic = "POSITIVE"
decision_strategy = "UNANIMOUS"
depends_on = [
keycloak_openid_client_permissions.realm-management_permission,
]
}
resource "keycloak_role" "role" {
name = "%s"
realm_id = "${keycloak_realm.realm.id}"
}
resource "keycloak_role_permissions" "my_permission" {
realm_id = keycloak_realm.realm.id
role_id = keycloak_role.role.id
map_role_scope_policy_id = keycloak_openid_client_user_policy.test.id
map_role_client_scope_scope_policy_id = keycloak_openid_client_user_policy.test.id
map_role_composite_scope_policy_id = keycloak_openid_client_user_policy.test.id
}
```

### Argument Reference

The following arguments are supported:

- `realm_id` - (Required) The realm this group exists in.
- `role_id` - (Required) The id of the role.
- `map_role_scope_policy_id` - (Optional) Policy id that will be set on the scope based map-role permission automatically created by enabling permissions on the reference role.
- `map_role_client_scope_scope_policy_id` - (Optional) Policy id that will be set on the scope based map-role-client-scope permission automatically created by enabling permissions on the reference role.
- `map_role_composite_scope_policy_id` - (Optional) Policy id that will be set on the scope based map-role-composite permission automatically created by enabling permissions on the reference role.


### Attributes Reference

In addition to the arguments listed above, the following computed attributes are exported:

- `enabled` - User permissions are Enabled (true)
- `authorization_resource_server_id` - Resource server id representing the realm management client on which this permission is managed.

38 changes: 38 additions & 0 deletions keycloak/roles_permissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package keycloak

import (
"fmt"
)

type RolePermissionsInput struct {
Enabled bool `json:"enabled"`
}

type RolePermissions struct {
RealmId string `json:"-"`
RoleId string `json:"-"`
Enabled bool `json:"enabled"`
Resource string `json:"resource"`
ScopePermissions map[string]interface{} `json:"scopePermissions"`
}

func (keycloakClient *KeycloakClient) EnableRolePermissions(realmId, clientId string) error {
return keycloakClient.put(fmt.Sprintf("/realms/%s/roles-by-id/%s/management/permissions", realmId, clientId), RolePermissionsInput{Enabled: true})
}

func (keycloakClient *KeycloakClient) DisableRolePermissions(realmId, clientId string) error {
return keycloakClient.put(fmt.Sprintf("/realms/%s/roles-by-id/%s/management/permissions", realmId, clientId), RolePermissionsInput{Enabled: false})
}

func (keycloakClient *KeycloakClient) GetRolePermissions(realmId, roleId string) (*RolePermissions, error) {
var rolePermissions RolePermissions
rolePermissions.RealmId = realmId
rolePermissions.RoleId = roleId

err := keycloakClient.get(fmt.Sprintf("/realms/%s/roles-by-id/%s/management/permissions", realmId, roleId), &rolePermissions, nil)
if err != nil {
return nil, err
}

return &rolePermissions, nil
}
1 change: 1 addition & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ func KeycloakProvider() *schema.Provider {
"keycloak_authentication_execution_config": resourceKeycloakAuthenticationExecutionConfig(),
"keycloak_identity_provider_token_exchange_scope_permission": resourceKeycloakIdentityProviderTokenExchangeScopePermission(),
"keycloak_openid_client_permissions": resourceKeycloakOpenidClientPermissions(),
"keycloak_role_permissions": resourceKeycloakRolePermissions(),
},
Schema: map[string]*schema.Schema{
"client_id": {
Expand Down
218 changes: 218 additions & 0 deletions provider/resource_keycloak_role_permissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package provider

import (
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mrparkers/terraform-provider-keycloak/keycloak"
)

func resourceKeycloakRolePermissions() *schema.Resource {
return &schema.Resource{
Create: resourceKeycloakRolePermissionsCreate,
Read: resourceKeycloakRolePermissionsRead,
Delete: resourceKeycloakRolePermissionsDelete,
Update: resourceKeycloakRolePermissionsUpdate,
Importer: &schema.ResourceImporter{
State: resourceKeycloakRolePermissionsImport,
},
Schema: map[string]*schema.Schema{
"realm_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"role_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"enabled": {
Type: schema.TypeBool,
Computed: true,
},
"authorization_resource_server_id": {
Type: schema.TypeString,
Computed: true,
Description: "Resource server id representing the realm management client on which this permission is managed",
},
"map_role_scope_policy_id": {
Type: schema.TypeString,
Optional: true,
},
"map_role_client_scope_scope_policy_id": {
Type: schema.TypeString,
Optional: true,
},
"map_role_composite_scope_policy_id": {
Type: schema.TypeString,
Optional: true,
}},
}
}

func rolePermissionsId(realmId, roleId string) string {
return fmt.Sprintf("%s/%s", realmId, roleId)
}

func setRoleScopePermissionPolicy(keycloakClient *keycloak.KeycloakClient, realmId, roleId string, scopeName string, policyId string) error {
rolePermissions, err := keycloakClient.GetRolePermissions(realmId, roleId)
if err != nil {
return err
}

realmManagementClient, err := keycloakClient.GetOpenidClientByClientId(realmId, "realm-management")
if err != nil {
return err
}

permission, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, rolePermissions.ScopePermissions[scopeName].(string))
if err != nil {
return err
}

permission.Policies = []string{policyId}

return keycloakClient.UpdateOpenidClientAuthorizationPermission(permission)
}

func unsetRoleScopePermissionPolicy(keycloakClient *keycloak.KeycloakClient, realmId, roleId, scopeName string) error {
rolePermissions, err := keycloakClient.GetRolePermissions(realmId, roleId)
if err != nil {
return err
}

realmManagementClient, err := keycloakClient.GetOpenidClientByClientId(realmId, "realm-management")
if err != nil {
return err
}

permission, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, rolePermissions.ScopePermissions[scopeName].(string))
if err != nil {
return err
}

permission.Policies = []string{}
err = keycloakClient.UpdateOpenidClientAuthorizationPermission(permission)
if err != nil {
return err
}

return nil
}

func resourceKeycloakRolePermissionsCreate(data *schema.ResourceData, meta interface{}) error {
return resourceKeycloakRolePermissionsUpdate(data, meta)
}

func resourceKeycloakRolePermissionsUpdate(data *schema.ResourceData, meta interface{}) error {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
roleId := data.Get("role_id").(string)

err := keycloakClient.EnableRolePermissions(realmId, roleId)
if err != nil {
return err
}

mapRolesScopePolicyId, ok := data.GetOk("map_role_scope_policy_id")
if ok && mapRolesScopePolicyId.(string) != "" {
err := setRoleScopePermissionPolicy(keycloakClient, realmId, roleId, "map-role", mapRolesScopePolicyId.(string))
if err != nil {
return err
}
}
mapRolesClientsScopePolicyId, ok := data.GetOk("map_role_client_scope_scope_policy_id")
if ok && mapRolesClientsScopePolicyId.(string) != "" {
err := setRoleScopePermissionPolicy(keycloakClient, realmId, roleId, "map-role-client-scope", mapRolesClientsScopePolicyId.(string))
if err != nil {
return err
}
}
mapRolesCompositeScopePolicyId, ok := data.GetOk("map_role_composite_scope_policy_id")
if ok && mapRolesCompositeScopePolicyId.(string) != "" {
err := setRoleScopePermissionPolicy(keycloakClient, realmId, roleId, "map-role-composite", mapRolesCompositeScopePolicyId.(string))
if err != nil {
return err
}
}

return resourceKeycloakRolePermissionsRead(data, meta)
}

func resourceKeycloakRolePermissionsRead(data *schema.ResourceData, meta interface{}) error {
keycloakClient := meta.(*keycloak.KeycloakClient)
realmId := data.Get("realm_id").(string)
roleId := data.Get("role_id").(string)

rolePermissions, err := keycloakClient.GetRolePermissions(realmId, roleId)
if err != nil {
return handleNotFoundError(err, data)
}

data.SetId(rolePermissionsId(rolePermissions.RealmId, rolePermissions.RoleId))
data.Set("realm_id", rolePermissions.RealmId)
data.Set("role_id", rolePermissions.RoleId)

data.Set("enabled", rolePermissions.Enabled)

realmManagementClient, err := keycloakClient.GetOpenidClientByClientId(realmId, "realm-management")
if err != nil {
return err
}

permissionMapRoles, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, rolePermissions.ScopePermissions["map-role"].(string))
if err != nil {
return err
}
if permissionMapRoles != nil && len(permissionMapRoles.Policies) > 0 {
data.Set("map_role_scope_policy_id", permissionMapRoles.Policies[0])
}
permissionMapRolesClientScope, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, rolePermissions.ScopePermissions["map-role-client-scope"].(string))
if err != nil {
return err
}
if permissionMapRolesClientScope != nil && len(permissionMapRolesClientScope.Policies) > 0 {
data.Set("map_role_client_scope_scope_policy_id", permissionMapRolesClientScope.Policies[0])
}
permissionMapRolesComposite, err := keycloakClient.GetOpenidClientAuthorizationPermission(realmId, realmManagementClient.Id, rolePermissions.ScopePermissions["map-role-composite"].(string))
if err != nil {
return err
}
if permissionMapRolesComposite != nil && len(permissionMapRolesComposite.Policies) > 0 {
data.Set("map_role_composite_scope_policy_id", permissionMapRolesComposite.Policies[0])
}

data.Set("authorization_resource_server_id", realmManagementClient.Id)

return nil
}

func resourceKeycloakRolePermissionsDelete(data *schema.ResourceData, meta interface{}) error {

keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
roleId := data.Get("role_id").(string)

rolePermissions, err := keycloakClient.GetRolePermissions(realmId, roleId)
if err == nil && rolePermissions.Enabled {
_ = unsetRoleScopePermissionPolicy(keycloakClient, realmId, roleId, "map-role")
}
return keycloakClient.DisableRolePermissions(realmId, roleId)
}

func resourceKeycloakRolePermissionsImport(d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
parts := strings.Split(d.Id(), "/")
if len(parts) != 2 {
return nil, fmt.Errorf("Invalid import. Supported import formats: {{realmId}}/{{roleId}}")
}
d.Set("realm_id", parts[0])
d.Set("role_id", parts[1])

d.SetId(rolePermissionsId(parts[0], parts[1]))

return []*schema.ResourceData{d}, nil
}
Loading

0 comments on commit 8967ec2

Please sign in to comment.