controllers/keycloakclient/chain/process_policy.go (274 lines of code) (raw):
package chain
import (
"context"
"fmt"
"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 policyLogKey = "policy"
type ProcessPolicy struct {
keycloakApiClient keycloak.Client
}
func NewProcessPolicy(keycloakApiClient keycloak.Client) *ProcessPolicy {
return &ProcessPolicy{keycloakApiClient: keycloakApiClient}
}
// Serve method for processing keycloak client policies.
func (h *ProcessPolicy) 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)
}
existingPolicies, err := h.keycloakApiClient.GetPolicies(ctx, realmName, clientID)
if err != nil {
return fmt.Errorf("failed to get policies: %w", err)
}
for i := 0; i < len(keycloakClient.Spec.Authorization.Policies); i++ {
log.Info("Processing policy", policyLogKey, keycloakClient.Spec.Authorization.Policies[i].Name)
var policyRepresentation *gocloak.PolicyRepresentation
if policyRepresentation, err = h.toPolicyRepresentation(ctx, &keycloakClient.Spec.Authorization.Policies[i], clientID, realmName); err != nil {
return fmt.Errorf("failed to convert policy: %w", err)
}
existingPolicy, ok := existingPolicies[keycloakClient.Spec.Authorization.Policies[i].Name]
if ok {
policyRepresentation.ID = existingPolicy.ID
if err = h.keycloakApiClient.UpdatePolicy(ctx, realmName, clientID, *policyRepresentation); err != nil {
return fmt.Errorf("failed to update policy: %w", err)
}
log.Info("Policy updated", policyLogKey, keycloakClient.Spec.Authorization.Policies[i].Name)
delete(existingPolicies, keycloakClient.Spec.Authorization.Policies[i].Name)
continue
}
if _, err = h.keycloakApiClient.CreatePolicy(ctx, realmName, clientID, *policyRepresentation); err != nil {
return fmt.Errorf("failed to create policy: %w", err)
}
log.Info("Policy created", policyLogKey, keycloakClient.Spec.Authorization.Policies[i].Name)
}
if err = h.deletePolicies(ctx, existingPolicies, realmName, clientID); err != nil {
return err
}
return nil
}
func (h *ProcessPolicy) deletePolicies(ctx context.Context, existingPolicies map[string]*gocloak.PolicyRepresentation, realmName string, clientID string) error {
log := ctrl.LoggerFrom(ctx)
for name := range existingPolicies {
if name == "Default Policy" {
continue
}
if err := h.keycloakApiClient.DeletePolicy(ctx, realmName, clientID, *existingPolicies[name].ID); err != nil {
if !adapter.IsErrNotFound(err) {
return fmt.Errorf("failed to delete policy: %w", err)
}
}
log.Info("Policy deleted", policyLogKey, name)
}
return nil
}
// toPolicyRepresentation converts keycloakApi.Policy to gocloak.PolicyRepresentation.
// nolint:cyclop // it's a conversion method, so it's ok
func (h *ProcessPolicy) toPolicyRepresentation(ctx context.Context, policy *keycloakApi.Policy, clientID, realm string) (*gocloak.PolicyRepresentation, error) {
keycloakPolicy := getBasePolicyRepresentation(policy)
switch policy.Type {
case keycloakApi.PolicyTypeAggregate:
if err := h.toAggregatePolicyRepresentation(ctx, policy, clientID, realm, keycloakPolicy); err != nil {
return nil, err
}
case "client":
if err := h.toClientPolicyRepresentation(ctx, policy, realm, keycloakPolicy); err != nil {
return nil, err
}
case keycloakApi.PolicyTypeGroup:
if err := h.toGroupPolicyRepresentation(ctx, policy, realm, keycloakPolicy); err != nil {
return nil, err
}
case keycloakApi.PolicyTypeRole:
if err := h.toRolePolicyRepresentation(ctx, policy, realm, keycloakPolicy); err != nil {
return nil, err
}
case keycloakApi.PolicyTypeTime:
if err := h.toTimePolicyRepresentation(ctx, policy, keycloakPolicy); err != nil {
return nil, err
}
case keycloakApi.PolicyTypeUser:
if err := h.toUserPolicyRepresentation(ctx, policy, realm, keycloakPolicy); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported policy type %s", policy.Type)
}
return keycloakPolicy, nil
}
func (h *ProcessPolicy) toAggregatePolicyRepresentation(
ctx context.Context,
policy *keycloakApi.Policy,
clientID string,
realm string,
policyRepresentation *gocloak.PolicyRepresentation,
) error {
if policy.AggregatedPolicy == nil {
return fmt.Errorf("aggregatedPolicy spec is not specified")
}
existingPolicies, err := h.keycloakApiClient.GetPolicies(ctx, realm, clientID)
if err != nil {
return fmt.Errorf("failed to get policies: %w", err)
}
aggregatedPolicies := make([]string, 0, len(policy.AggregatedPolicy.Policies))
for _, p := range policy.AggregatedPolicy.Policies {
existingPolicy, ok := existingPolicies[p]
if !ok {
return fmt.Errorf("policy %s does not exist", p)
}
if existingPolicy.ID == nil {
return fmt.Errorf("policy %s does not have ID", p)
}
aggregatedPolicies = append(aggregatedPolicies, *existingPolicy.ID)
}
policyRepresentation.AggregatedPolicyRepresentation.Policies = &aggregatedPolicies
return nil
}
func (h *ProcessPolicy) toClientPolicyRepresentation(ctx context.Context, policy *keycloakApi.Policy, realm string, keycloakPolicy *gocloak.PolicyRepresentation) error {
if policy.ClientPolicy == nil {
return fmt.Errorf("clientPolicy spec is not specified")
}
existingClients, err := h.keycloakApiClient.GetClients(ctx, realm)
if err != nil {
return fmt.Errorf("failed to get clients: %w", err)
}
clientPolicy := make([]string, 0, len(policy.ClientPolicy.Clients))
for _, c := range policy.ClientPolicy.Clients {
existingClient, ok := existingClients[c]
if !ok {
return fmt.Errorf("client %s does not exist", c)
}
if existingClient.ID == nil {
return fmt.Errorf("client %s does not have ID", c)
}
clientPolicy = append(clientPolicy, *existingClient.ID)
}
keycloakPolicy.ClientPolicyRepresentation.Clients = &clientPolicy
return nil
}
func (h *ProcessPolicy) toGroupPolicyRepresentation(ctx context.Context, policy *keycloakApi.Policy, realm string, keycloakPolicy *gocloak.PolicyRepresentation) error {
if policy.GroupPolicy == nil {
return fmt.Errorf("group spec is not specified")
}
existingGroups, err := h.keycloakApiClient.GetGroups(ctx, realm)
if err != nil {
return fmt.Errorf("failed to get groups: %w", err)
}
groupPolicy := make([]gocloak.GroupDefinition, 0, len(policy.GroupPolicy.Groups))
for _, g := range policy.GroupPolicy.Groups {
existingGroup, ok := existingGroups[g.Name]
if !ok {
return fmt.Errorf("group %s does not exist", g.Name)
}
if existingGroup.ID == nil {
return fmt.Errorf("group %s does not have ID", g.Name)
}
extendChildren := g.ExtendChildren
groupPolicy = append(groupPolicy, gocloak.GroupDefinition{
ID: existingGroup.ID,
ExtendChildren: &extendChildren,
})
}
groupsClaim := policy.GroupPolicy.GroupsClaim
keycloakPolicy.GroupPolicyRepresentation = gocloak.GroupPolicyRepresentation{
Groups: &groupPolicy,
GroupsClaim: &groupsClaim,
}
return nil
}
func (h *ProcessPolicy) toRolePolicyRepresentation(ctx context.Context, policy *keycloakApi.Policy, realm string, keycloakPolicy *gocloak.PolicyRepresentation) error {
if policy.RolePolicy == nil {
return fmt.Errorf("role spec is not specified")
}
existingRoles, err := h.keycloakApiClient.GetRealmRoles(ctx, realm)
if err != nil {
return fmt.Errorf("failed to get realm roles: %w", err)
}
rolePolicy := make([]gocloak.RoleDefinition, 0, len(policy.RolePolicy.Roles))
for _, r := range policy.RolePolicy.Roles {
existingRole, ok := existingRoles[r.Name]
if !ok {
return fmt.Errorf("role %s does not exist", r.Name)
}
if existingRole.ID == nil {
return fmt.Errorf("role %s does not have ID", r.Name)
}
required := r.Required
rolePolicy = append(rolePolicy, gocloak.RoleDefinition{
ID: existingRole.ID,
Required: &required,
})
}
keycloakPolicy.RolePolicyRepresentation.Roles = &rolePolicy
return nil
}
func (h *ProcessPolicy) toTimePolicyRepresentation(ctx context.Context, policy *keycloakApi.Policy, keycloakPolicy *gocloak.PolicyRepresentation) error {
if policy.TimePolicy == nil {
return fmt.Errorf("time spec is not specified")
}
notBefore := policy.TimePolicy.NotBefore
notOnOrAfter := policy.TimePolicy.NotOnOrAfter
dayMonth := policy.TimePolicy.DayMonth
dayMonthEnd := policy.TimePolicy.DayMonthEnd
month := policy.TimePolicy.Month
monthEnd := policy.TimePolicy.MonthEnd
hour := policy.TimePolicy.Hour
hourEnd := policy.TimePolicy.HourEnd
minute := policy.TimePolicy.Minute
minuteEnd := policy.TimePolicy.MinuteEnd
keycloakPolicy.TimePolicyRepresentation = gocloak.TimePolicyRepresentation{
NotBefore: ¬Before,
NotOnOrAfter: ¬OnOrAfter,
DayMonth: &dayMonth,
DayMonthEnd: &dayMonthEnd,
Month: &month,
MonthEnd: &monthEnd,
Hour: &hour,
HourEnd: &hourEnd,
Minute: &minute,
MinuteEnd: &minuteEnd,
}
return nil
}
func (h *ProcessPolicy) toUserPolicyRepresentation(ctx context.Context, policy *keycloakApi.Policy, realm string, keycloakPolicy *gocloak.PolicyRepresentation) error {
if policy.UserPolicy == nil {
return fmt.Errorf("user spec is not specified")
}
existingUsers, err := h.keycloakApiClient.GetUsersByNames(ctx, realm, policy.UserPolicy.Users)
if err != nil {
return fmt.Errorf("failed to get users: %w", err)
}
userPolicy := make([]string, 0, len(policy.UserPolicy.Users))
for _, u := range policy.UserPolicy.Users {
existingUser, ok := existingUsers[u]
if !ok {
return fmt.Errorf("user %s does not exist", u)
}
if existingUser.ID == nil {
return fmt.Errorf("user %s does not have ID", u)
}
userPolicy = append(userPolicy, *existingUser.ID)
}
keycloakPolicy.UserPolicyRepresentation.Users = &userPolicy
return nil
}
func getBasePolicyRepresentation(policy *keycloakApi.Policy) *gocloak.PolicyRepresentation {
keycloakPolicy := &gocloak.PolicyRepresentation{}
name := policy.Name
keycloakPolicy.Name = &name
pType := policy.Type
keycloakPolicy.Type = &pType
desc := policy.Description
decisionStrategy := gocloak.DecisionStrategy(policy.DecisionStrategy)
keycloakPolicy.DecisionStrategy = &decisionStrategy
keycloakPolicy.Description = &desc
logic := gocloak.Logic(policy.Logic)
keycloakPolicy.Logic = &logic
return keycloakPolicy
}