pkg/client/keycloak/adapter/gocloak_adapter_client_scope.go (303 lines of code) (raw):
package adapter
import (
"context"
"fmt"
"strings"
"github.com/Nerzal/gocloak/v12"
"github.com/pkg/errors"
)
const (
OpenIdProtocol = "openid-connect"
OIDCAudienceMapper = "oidc-audience-mapper"
)
type ClientScope struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
Attributes map[string]string `json:"attributes"`
Protocol string `json:"protocol"`
ProtocolMappers []ProtocolMapper `json:"protocolMappers"`
Default bool `json:"-"`
}
type ProtocolMapper struct {
Name string `json:"name"`
Protocol string `json:"protocol"`
ProtocolMapper string `json:"protocolMapper"`
Config map[string]string `json:"config"`
}
func (a GoCloakAdapter) CreateClientScope(ctx context.Context, realmName string, scope *ClientScope) (string, error) {
rsp, err := a.startRestyRequest().
SetContext(ctx).
SetPathParams(map[string]string{
keycloakApiParamRealm: realmName,
}).
SetBody(scope).
Post(a.buildPath(postClientScope))
if err = a.checkError(err, rsp); err != nil {
return "", errors.Wrap(err, "unable to create client scope")
}
id, err := getIDFromResponseLocation(rsp.RawResponse)
if err != nil {
return "", errors.Wrap(err, "unable to get flow id")
}
if scope.Default {
if err := a.setDefaultClientScopeForRealm(ctx, realmName, id); err != nil {
return id, errors.Wrap(err, "unable to set default client scope for realm")
}
}
return id, nil
}
func (a GoCloakAdapter) UpdateClientScope(ctx context.Context, realmName, scopeID string, scope *ClientScope) error {
if err := a.syncClientScopeProtocolMappers(ctx, realmName, scopeID, scope.ProtocolMappers); err != nil {
return errors.Wrap(err, "unable to sync client scope protocol mappers")
}
rsp, err := a.startRestyRequest().
SetContext(ctx).
SetPathParams(map[string]string{
keycloakApiParamRealm: realmName,
keycloakApiParamId: scopeID,
}).
SetBody(scope).
Put(a.buildPath(putClientScope))
if err = a.checkError(err, rsp); err != nil {
return errors.Wrap(err, "unable to update client scope")
}
needToUpdateDefault, err := a.needToUpdateDefault(ctx, realmName, scope)
if err != nil {
return errors.Wrap(err, "unable to check if need to update default")
}
if !needToUpdateDefault {
return nil
}
if scope.Default {
if err := a.setDefaultClientScopeForRealm(ctx, realmName, scopeID); err != nil {
return errors.Wrap(err, "unable to set default client scope for realm")
}
return nil
}
if err := a.unsetDefaultClientScopeForRealm(ctx, realmName, scopeID); err != nil {
return errors.Wrap(err, "unable to unset default client scope for realm")
}
return nil
}
func (a GoCloakAdapter) needToUpdateDefault(ctx context.Context, realmName string, scope *ClientScope) (bool, error) {
defaultScopes, err := a.GetDefaultClientScopesForRealm(ctx, realmName)
if err != nil {
return false, errors.Wrap(err, "unable to get default client scopes")
}
currentScopeDefaultState := false
for _, s := range defaultScopes {
if s.Name == scope.Name {
currentScopeDefaultState = true
break
}
}
return currentScopeDefaultState != scope.Default, nil
}
// TODO: add context.
func (a GoCloakAdapter) GetClientScope(scopeName, realmName string) (*ClientScope, error) {
log := a.log.WithValues("scopeName", scopeName, logKeyRealm, realmName)
log.Info("Start get Client Scope...")
var result []ClientScope
resp, err := a.client.RestyClient().R().
SetAuthToken(a.token.AccessToken).
SetHeader(contentTypeHeader, contentTypeJson).
SetPathParams(map[string]string{
keycloakApiParamRealm: realmName,
}).
SetResult(&result).
Get(a.buildPath(getRealmClientScopes))
if err = a.checkError(err, resp); err != nil {
return nil, err
}
if result == nil {
return nil, NotFoundError(fmt.Sprintf("realm %v doesnt contain client scopes, rsp: %s", realmName, resp.String()))
}
scope, err := getClientScope(scopeName, result)
if err != nil {
return nil, err
}
log.Info("End get Client Scope", "scope", scope)
return scope, err
}
func getClientScope(name string, clientScopes []ClientScope) (*ClientScope, error) {
for _, cs := range clientScopes {
if cs.Name == name {
return &cs, nil
}
}
return nil, NotFoundError(fmt.Sprintf("scope %v was not found", name))
}
func (a GoCloakAdapter) GetClientScopesByNames(ctx context.Context, realmName string, scopeNames []string) ([]ClientScope, error) {
log := a.log.WithValues("scopeNames", strings.Join(scopeNames, ","), "realm", realmName)
log.Info("Start get Client Scopes by name...")
var result []ClientScope
resp, err := a.client.RestyClient().R().
SetContext(ctx).
SetAuthToken(a.token.AccessToken).
SetHeader(contentTypeHeader, contentTypeJson).
SetPathParams(map[string]string{
keycloakApiParamRealm: realmName,
}).
SetResult(&result).
Get(a.buildPath(getRealmClientScopes))
if err = a.checkError(err, resp); err != nil {
return nil, err
}
log.Info("End get Client Scopes...")
return a.filterClientScopes(scopeNames, result)
}
func (a GoCloakAdapter) filterClientScopes(scopeNames []string, clientScopes []ClientScope) ([]ClientScope, error) {
clientScopesMap := make(map[string]ClientScope)
for _, s := range clientScopes {
clientScopesMap[s.Name] = s
}
result := make([]ClientScope, 0, len(scopeNames))
missingScopes := make([]string, 0, len(scopeNames))
for _, scopeName := range scopeNames {
scope, ok := clientScopesMap[scopeName]
if ok {
result = append(result, scope)
continue
}
missingScopes = append(missingScopes, scopeName)
}
if len(missingScopes) > 0 {
return nil, fmt.Errorf("failed to get '%s' keycloak client scopes", strings.Join(missingScopes, ","))
}
return result, nil
}
func (a GoCloakAdapter) DeleteClientScope(ctx context.Context, realmName, scopeID string) error {
if err := a.unsetDefaultClientScopeForRealm(ctx, realmName, scopeID); err != nil {
return errors.Wrap(err, "unable to unset default client scope for realm")
}
if err := a.client.DeleteClientScope(ctx, a.token.AccessToken, realmName, scopeID); err != nil {
return errors.Wrap(err, "unable to delete client scope")
}
return nil
}
func (a GoCloakAdapter) syncClientScopeProtocolMappers(ctx context.Context, realm, scopeID string,
instanceProtocolMappers []ProtocolMapper) error {
scope, err := a.client.GetClientScope(ctx, a.token.AccessToken, realm, scopeID)
if err != nil {
return errors.Wrap(err, "unable to get client scope")
}
if scope.ProtocolMappers != nil {
for _, pm := range *scope.ProtocolMappers {
rsp, err := a.startRestyRequest().
SetContext(ctx).
SetPathParams(map[string]string{
keycloakApiParamRealm: realm,
keycloakApiParamClientScopeId: scopeID,
"protocolMapperID": *pm.ID,
}).
Delete(a.buildPath(deleteClientScopeProtocolMapper))
if err = a.checkError(err, rsp); err != nil {
return errors.Wrap(err, "error during client scope protocol mapper deletion")
}
}
}
for _, pm := range instanceProtocolMappers {
rsp, err := a.startRestyRequest().
SetContext(ctx).
SetPathParams(map[string]string{
keycloakApiParamRealm: realm,
keycloakApiParamClientScopeId: scopeID,
}).
SetBody(&pm).
Post(a.buildPath(createClientScopeProtocolMapper))
if err = a.checkError(err, rsp); err != nil {
return errors.Wrap(err, "error during client scope protocol mapper creation")
}
}
return nil
}
func (a GoCloakAdapter) GetDefaultClientScopesForRealm(ctx context.Context, realmName string) ([]ClientScope, error) {
var scopes []ClientScope
rsp, err := a.startRestyRequest().
SetContext(ctx).
SetPathParams(map[string]string{
keycloakApiParamRealm: realmName,
}).
SetResult(&scopes).
Get(a.buildPath(getDefaultClientScopes))
if err = a.checkError(err, rsp); err != nil {
return nil, errors.Wrap(err, "unable to get default client scopes for realm")
}
return scopes, nil
}
func (a GoCloakAdapter) setDefaultClientScopeForRealm(ctx context.Context, realm, scopeID string) error {
rsp, err := a.startRestyRequest().
SetContext(ctx).
SetPathParams(map[string]string{
keycloakApiParamRealm: realm,
keycloakApiParamClientScopeId: scopeID,
}).
SetBody(map[string]string{
keycloakApiParamRealm: realm,
keycloakApiParamClientScopeId: scopeID,
}).
Put(a.buildPath(putDefaultClientScope))
if err = a.checkError(err, rsp); err != nil {
return errors.Wrap(err, "unable to set default client scope for realm")
}
return nil
}
func (a GoCloakAdapter) unsetDefaultClientScopeForRealm(ctx context.Context, realm, scopeID string) error {
rsp, err := a.startRestyRequest().
SetContext(ctx).
SetPathParams(map[string]string{
keycloakApiParamRealm: realm,
keycloakApiParamClientScopeId: scopeID,
}).
Delete(a.buildPath(deleteDefaultClientScope))
if err = a.checkError(err, rsp); err != nil {
return errors.Wrap(err, "unable to unset default client scope for realm")
}
return nil
}
func (a GoCloakAdapter) GetClientScopeMappers(ctx context.Context, realmName, scopeID string) ([]ProtocolMapper, error) {
var mappers []ProtocolMapper
rsp, err := a.startRestyRequest().
SetContext(ctx).
SetPathParams(map[string]string{
keycloakApiParamRealm: realmName,
"scopeId": scopeID,
}).
SetResult(&mappers).
Get(a.buildPath(postClientScopeMapper))
if err = a.checkError(err, rsp); err != nil {
return nil, errors.Wrap(err, "unable to get client scope mappers")
}
return mappers, nil
}
func (a GoCloakAdapter) GetClientScopes(ctx context.Context, realm string) (map[string]gocloak.ClientScope, error) {
scopes, err := a.client.GetClientScopes(ctx, a.token.AccessToken, realm)
if err != nil {
return nil, fmt.Errorf("failed to get client scopes: %w", err)
}
sc := make(map[string]gocloak.ClientScope, len(scopes))
for _, s := range scopes {
if s != nil && s.Name != nil {
sc[*s.Name] = *s
}
}
return sc, nil
}
func (a GoCloakAdapter) PutClientScopeMapper(realmName, scopeID string, protocolMapper *ProtocolMapper) error {
log := a.log.WithValues("scopeId", scopeID, logKeyRealm, realmName)
log.Info("Start put Client Scope mapper...")
resp, err := a.client.RestyClient().R().
SetAuthToken(a.token.AccessToken).
SetHeader(contentTypeHeader, contentTypeJson).
SetPathParams(map[string]string{
keycloakApiParamRealm: realmName,
"scopeId": scopeID,
}).
SetBody(protocolMapper).
Post(a.buildPath(postClientScopeMapper))
if err = a.checkError(err, resp); err != nil {
return errors.Wrap(err, "unable to put client scope mapper")
}
log.Info("Client Scope mapper was successfully configured!")
return nil
}