controllers/keycloakclient/chain/process_resources.go (124 lines of code) (raw):
package chain
import (
"context"
"fmt"
"maps"
"slices"
"github.com/Nerzal/gocloak/v12"
ctrl "sigs.k8s.io/controller-runtime"
keycloakApi "github.com/epam/edp-keycloak-operator/api/v1"
"github.com/epam/edp-keycloak-operator/pkg/client/keycloak"
"github.com/epam/edp-keycloak-operator/pkg/client/keycloak/adapter"
)
const resourceLogKey = "resource"
type ProcessResources struct {
keycloakApiClient keycloak.Client
}
func NewProcessResources(keycloakApiClient keycloak.Client) *ProcessResources {
return &ProcessResources{keycloakApiClient: keycloakApiClient}
}
func (h *ProcessResources) Serve(ctx context.Context, keycloakClient *keycloakApi.KeycloakClient, realmName string) error {
log := ctrl.LoggerFrom(ctx)
if keycloakClient.Spec.Authorization == nil {
log.Info("Authorization settings are not specified")
return nil
}
clientID, err := h.keycloakApiClient.GetClientID(keycloakClient.Spec.ClientId, realmName)
if err != nil {
return fmt.Errorf("failed to get client id: %w", err)
}
existingResources, err := h.keycloakApiClient.GetResources(ctx, realmName, clientID)
if err != nil {
return fmt.Errorf("failed to get resources: %w", err)
}
for i := 0; i < len(keycloakClient.Spec.Authorization.Resources); i++ {
log.Info("Processing resource", resourceLogKey, keycloakClient.Spec.Authorization.Resources[i].Name)
var resourceRepresentation *gocloak.ResourceRepresentation
if resourceRepresentation, err = h.toResourceRepresentation(ctx, &keycloakClient.Spec.Authorization.Resources[i], clientID, realmName); err != nil {
return fmt.Errorf("failed to convert resource: %w", err)
}
existingResource, ok := existingResources[keycloakClient.Spec.Authorization.Resources[i].Name]
if ok {
resourceRepresentation.ID = existingResource.ID
if err = h.keycloakApiClient.UpdateResource(ctx, realmName, clientID, *resourceRepresentation); err != nil {
return fmt.Errorf("failed to update resource: %w", err)
}
log.Info("Resource updated", resourceLogKey, keycloakClient.Spec.Authorization.Resources[i].Name)
delete(existingResources, keycloakClient.Spec.Authorization.Resources[i].Name)
continue
}
if _, err = h.keycloakApiClient.CreateResource(ctx, realmName, clientID, *resourceRepresentation); err != nil {
return fmt.Errorf("failed to create resource: %w", err)
}
log.Info("Resource created", resourceLogKey, keycloakClient.Spec.Authorization.Resources[i].Name)
}
if err = h.deleteResources(ctx, existingResources, realmName, clientID); err != nil {
return err
}
return nil
}
func (h *ProcessResources) deleteResources(ctx context.Context, existingResources map[string]gocloak.ResourceRepresentation, realmName string, clientID string) error {
log := ctrl.LoggerFrom(ctx)
for name := range existingResources {
if name == "Default Resource" {
continue
}
if err := h.keycloakApiClient.DeleteResource(ctx, realmName, clientID, *existingResources[name].ID); err != nil {
if !adapter.IsErrNotFound(err) {
return fmt.Errorf("failed to delete resource: %w", err)
}
}
log.Info("Resource deleted", resourceLogKey, name)
}
return nil
}
// toResourceRepresentation converts keycloakApi.Resource to gocloak.ResourceRepresentation.
func (h *ProcessResources) toResourceRepresentation(ctx context.Context, resource *keycloakApi.Resource, clientID, realm string) (*gocloak.ResourceRepresentation, error) {
keycloakResource := getBaseResourceRepresentation(resource)
if err := h.mapScopes(ctx, resource, keycloakResource, realm, clientID); err != nil {
return nil, fmt.Errorf("failed to map scopes: %w", err)
}
return keycloakResource, nil
}
func (h *ProcessResources) mapScopes(
ctx context.Context,
resource *keycloakApi.Resource,
keycloakResource *gocloak.ResourceRepresentation,
realm,
clientID string,
) error {
if len(resource.Scopes) == 0 {
keycloakResource.Scopes = &[]gocloak.ScopeRepresentation{}
return nil
}
existingScopes, err := h.keycloakApiClient.GetScopes(ctx, realm, clientID)
if err != nil {
return fmt.Errorf("failed to get scopes: %w", err)
}
resourceScopes := make([]gocloak.ScopeRepresentation, 0, len(resource.Scopes))
for _, r := range resource.Scopes {
existingScope, ok := existingScopes[r]
if !ok {
return fmt.Errorf("scope %s does not exist", r)
}
if existingScope.ID == nil {
return fmt.Errorf("scope %s does not have ID", r)
}
resourceScopes = append(resourceScopes, existingScope)
}
keycloakResource.Scopes = &resourceScopes
return nil
}
func getBaseResourceRepresentation(resource *keycloakApi.Resource) *gocloak.ResourceRepresentation {
r := &gocloak.ResourceRepresentation{
Name: &resource.Name,
DisplayName: &resource.DisplayName,
Type: &resource.Type,
IconURI: &resource.IconURI,
OwnerManagedAccess: &resource.OwnerManagedAccess,
}
uris := slices.Clone(resource.URIs)
r.URIs = &uris
attributes := make(map[string][]string, len(resource.Attributes))
maps.Copy(attributes, resource.Attributes)
return r
}