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) CreateRealm(ctx context.Context, realm gocloak.RealmRepresentation) error { _, err := kc.client.CreateRealm(ctx, kc.getToken(), realm) return err } func (kc *Keycloak) UpdateRealm(ctx context.Context, realm gocloak.RealmRepresentation) error { return kc.client.UpdateRealm(ctx, kc.getToken(), realm) } func (kc *Keycloak) CreateProvider(ctx context.Context, realm string, provider gocloak.IdentityProviderRepresentation) error { _, err := kc.client.CreateIdentityProvider(ctx, kc.getToken(), realm, provider) return err } func (kc *Keycloak) CreateClient(ctx context.Context, realm string, c gocloak.Client) error { _, err := kc.client.CreateClient(ctx, kc.getToken(), realm, c) return err } func (kc *Keycloak) UpdateClient(ctx context.Context, realm string, c gocloak.Client) error { return kc.client.UpdateClient(ctx, kc.getToken(), realm, c) } 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 }