99 lines
2.4 KiB
Go
99 lines
2.4 KiB
Go
|
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
|
||
|
}
|