package keycloak import ( "context" "log" "sync" "time" "github.com/Nerzal/gocloak/v7" ) // Keycloak contains all information to administrate keycloak // and stay logged in type Keycloak struct { mutex sync.Mutex client gocloak.GoCloak accessToken string validUntil time.Time user, pass, realm string } func (kc *Keycloak) CreateRealmIfNotExists(ctx context.Context, realm gocloak.RealmRepresentation) error { _, err := kc.client.CreateRealm(ctx, kc.getToken(), realm) if isConflict(err) { log.Printf("Realm '%s' already exists, not updated", *realm.Realm) return nil } return err } func (kc *Keycloak) CreateProviderIfNotExists(ctx context.Context, realm string, provider gocloak.IdentityProviderRepresentation) error { _, err := kc.client.CreateIdentityProvider(ctx, kc.getToken(), realm, provider) if isConflict(err) { log.Printf("Provider '%s/%s' already exists, not updated", realm, *provider.Alias) return nil } return err } func (kc *Keycloak) CreateClientIfNotExists(ctx context.Context, realm string, c gocloak.Client) error { _, err := kc.client.CreateClient(ctx, kc.getToken(), realm, c) if isConflict(err) { log.Printf("Client '%s/%s' already exists, not updated", realm, *c.ClientID) return nil } return err } func New(url, user, pass, realm string) (*Keycloak, error) { kc := &Keycloak{} kc.client = gocloak.NewClient(url) kc.user = user kc.pass = pass kc.realm = realm jwt, err := kc.createToken() kc.accessToken = jwt.AccessToken kc.validUntil = time.Now().Add(time.Duration(jwt.ExpiresIn) * time.Second) return kc, err } func (kc *Keycloak) getToken() string { kc.mutex.Lock() defer kc.mutex.Unlock() // If token is not valid 30 seconds in the future, // we need to create a new one if time.Now().Add(30 * time.Second).After(kc.validUntil) { jwt, err := kc.createToken() if err != nil { log.Fatalf("Valid credentials became invalid: %s", err) } kc.accessToken = jwt.AccessToken kc.validUntil = time.Now().Add(time.Duration(jwt.ExpiresIn) * time.Second) } return kc.accessToken } func (kc *Keycloak) createToken() (*gocloak.JWT, error) { token, err := kc.client.LoginAdmin(context.Background(), kc.user, kc.pass, kc.realm) if err != nil { return nil, err } return token, nil } func isConflict(err error) bool { if e, ok := err.(*gocloak.APIError); ok { return (e.Code == 409) } return false }