scim/pkg/supplier/keycloak.go

115 lines
3 KiB
Go

package supplier
import (
"context"
"fmt"
"net/url"
"strings"
"github.com/Nerzal/gocloak/v11"
"github.com/google/uuid"
)
type KeycloakSupplier struct {
host string
realm string
username string
password string
}
func NewKeycloakSupplier(u *url.URL) (KeycloakSupplier, error) {
if err := validateUrl(u); err != nil {
return KeycloakSupplier{}, nil
}
if u.Path == "" {
return KeycloakSupplier{}, fmt.Errorf("a realm must be provider")
}
username := u.User.Username()
password, _ := u.User.Password()
realm := strings.ReplaceAll(u.Path, "/", "")
u.Path = ""
u.User = nil
return KeycloakSupplier{
username: username,
password: password,
realm: realm,
host: u.String(),
}, nil
}
type ScimClient struct {
Name string
Endpoint string
Username string
Password string
}
func (r *KeycloakSupplier) login(ctx context.Context) (gocloak.GoCloak, *gocloak.JWT, error) {
client := gocloak.NewClient(r.host)
token, err := client.LoginAdmin(ctx, r.username, r.password, r.realm)
if err != nil {
return nil, nil, err
}
return client, token, nil
}
func (r *KeycloakSupplier) Reconcile(ctx context.Context, scim ScimClient) error {
client, token, err := r.login(ctx)
if err != nil {
return err
}
var component gocloak.Component
var exists bool
components, err := client.GetComponents(ctx, token.AccessToken, r.realm)
if err != nil {
return err
}
for _, v := range components {
if *v.ProviderType == "org.keycloak.storage.UserStorageProvider" && *v.ProviderID == "scim" && *v.Name == scim.Name {
component = *v
exists = true
break
}
}
componentConfig := map[string][]string{
"endpoint": {scim.Endpoint},
"auth-mode": {"BASIC_AUTH"},
"auth-user": {scim.Username},
"auth-pass": {scim.Password},
}
if !exists {
realm, err := client.GetRealm(ctx, token.AccessToken, r.realm)
if err != nil {
return err
}
pId := "scim"
pType := "org.keycloak.storage.UserStorageProvider"
providerId := uuid.NewString()
component = gocloak.Component{
ID: &providerId,
ParentID: realm.ID,
Name: &scim.Name,
ProviderID: &pId,
ProviderType: &pType,
}
componentConfig["priority"] = []string{"0"}
componentConfig["content-type"] = []string{"application/json"}
componentConfig["sync-import"] = []string{"false"}
componentConfig["sync-import-action"] = []string{"CREATE_LOCAL"}
componentConfig["propagation-user"] = []string{"true"}
componentConfig["propagation-group"] = []string{"true"}
}
component.ComponentConfig = &componentConfig
// (*component.ComponentConfig)["endpoint"] = []string{scim.Endpoint}
// (*component.ComponentConfig)["auth-mode"] = []string{"BASIC_AUTH"}
// (*component.ComponentConfig)["auth-username"] = []string{scim.Username}
// (*component.ComponentConfig)["auth-pass"] = []string{scim.Password}
if !exists {
_, err = client.CreateComponent(ctx, token.AccessToken, r.realm, component)
} else {
_, err = client.UpdateComponent(ctx, token.AccessToken, r.realm, component)
}
return err
}