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 }