From e15e6573f715aac08af8260e9c3cae5f25ea459f Mon Sep 17 00:00:00 2001 From: Johan Droz Date: Tue, 27 Feb 2018 16:30:04 +0100 Subject: [PATCH 1/8] Add routes for clients --- client.go | 6 ++-- clients.go | 41 +++++++++++++++++++++++++++ clients_test.go | 49 ++++++++++++++++++++++++++++++++ definitions.go | 74 ++++++++++++++++++++++++------------------------- 4 files changed, 130 insertions(+), 40 deletions(-) create mode 100644 clients.go create mode 100644 clients_test.go diff --git a/client.go b/client.go index 34fa462..2588180 100644 --- a/client.go +++ b/client.go @@ -43,9 +43,9 @@ func New(config Config) (*Client, error) { } } - if u.Scheme != "http" { - return nil, fmt.Errorf("protocol not supported, your address must start with http://, not %v", u.Scheme) - } + // if u.Scheme != "http" { + // return nil, fmt.Errorf("protocol not supported, your address must start with http://, not %v", u.Scheme) + // } var httpClient = gentleman.New() { diff --git a/clients.go b/clients.go new file mode 100644 index 0000000..d22a02c --- /dev/null +++ b/clients.go @@ -0,0 +1,41 @@ +package keycloak + +import ( + "fmt" + + "gopkg.in/h2non/gentleman.v2/plugins/url" +) + +const ( + clientsPath = "/auth/admin/realms/:realm/clients" + clientIDPath = clientsPath + "/:id" + clientSecret = clientsPath + "/client-secret" +) + +// GetClients returns a list of clients belonging to the realm. +// Parameters: clientId (filter by clientId), +// viewableOnly (filter clients that cannot be viewed in full by admin, default="false") +func (c *Client) GetClients(realmName string, paramKV ...string) ([]ClientRepresentation, error) { + if len(paramKV)%2 != 0 { + return nil, fmt.Errorf("the number of key/val parameters should be even") + } + + var resp = []ClientRepresentation{} + var plugins = append(createQueryPlugins(paramKV...), url.Path(clientsPath), url.Param("realm", realmName)) + var err = c.get(&resp, plugins...) + return resp, err +} + +// GetClient get the representation of the client. idClient is the id of client (not client-id). +func (c *Client) GetClient(realmName, idClient string) (ClientRepresentation, error) { + var resp = ClientRepresentation{} + var err = c.get(&resp, url.Path(clientIDPath), url.Param("realm", realmName), url.Param("id", idClient)) + return resp, err +} + +// GetSecret get the client secret. idClient is the id of client (not client-id). +func (c *Client) GetSecret(realmName, idClient string) (CredentialRepresentation, error) { + var resp = CredentialRepresentation{} + var err = c.get(&resp, url.Path(clientSecret), url.Param("realm", realmName), url.Param("id", idClient)) + return resp, err +} diff --git a/clients_test.go b/clients_test.go new file mode 100644 index 0000000..a4e746c --- /dev/null +++ b/clients_test.go @@ -0,0 +1,49 @@ +package keycloak + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func initTest(t *testing.T) *Client { + var config = Config{ + Addr: "http://127.0.0.1", + Username: "admin", + Password: "admin", + Timeout: time.Second * 20, + } + var client *Client + { + var err error + client, err = New(config) + require.Nil(t, err, "could not create client") + } + return client +} + +func TestCreateRealm(t *testing.T) { + var client = initTest(t) + var clients, err = client.GetClients("master") + for i, c := range clients { + fmt.Println(i, *(c.Id), *c.ClientId) + } + assert.Nil(t, err) +} + +func TestGetClient(t *testing.T) { + var client = initTest(t) + var c, err = client.GetClient("318ab6db-c056-4d2f-b4f6-c0b585ee45b3", "master") + fmt.Println(*(c.Id), *c.ClientId, c.Secret) + assert.Nil(t, err) +} + +func TestGetSecret(t *testing.T) { + var client = initTest(t) + var c, err = client.GetSecret("318ab6db-c056-4d2f-b4f6-c0b585ee45b3", "master") + fmt.Println(*(c.Value)) + assert.Nil(t, err) +} diff --git a/definitions.go b/definitions.go index 9f248bf..f8e3e8d 100644 --- a/definitions.go +++ b/definitions.go @@ -107,43 +107,43 @@ type ClientMappingsRepresentation struct { } type ClientRepresentation struct { - Access *map[string]interface{} `json:"access,omitempty"` - AdminUrl *string `json:"adminUrl,omitempty"` - Attributes *map[string]interface{} `json:"attributes,omitempty"` - AuthorizationServicesEnabled *bool `json:"authorizationServicesEnabled,omitempty"` - AuthorizationSettings *ResourceServerRepresentation `json:"authorizationSettings,omitempty"` - BaseUrl *string `json:"baseUrl,omitempty"` - BearerOnly *bool `json:"bearerOnly,omitempty"` - ClientAuthenticatorType *string `json:"clientAuthenticatorType,omitempty"` - ClientId *string `json:"clientId,omitempty"` - ClientTemplate *string `json:"clientTemplate,omitempty"` - ConsentRequired *bool `json:"consentRequired,omitempty"` - DefaultRoles *[]string `json:"defaultRoles,omitempty"` - Description *string `json:"description,omitempty"` - DirectAccessGrantsEnabled *bool `json:"directAccessGrantsEnabled,omitempty"` - Enabled *bool `json:"enabled,omitempty"` - FrontchannelLogout *bool `json:"frontchannelLogout,omitempty"` - FullScopeAllowed *bool `json:"fullScopeAllowed,omitempty"` - Id *string `json:"id,omitempty"` - ImplicitFlowEnabled *bool `json:"implicitFlowEnabled,omitempty"` - Name *string `json:"name,omitempty"` - NodeReRegistrationTimeout *int32 `json:"nodeReRegistrationTimeout,omitempty"` - NotBefore *int32 `json:"notBefore,omitempty"` - Protocol *string `json:"protocol,omitempty"` - ProtocolMappers *ProtocolMapperRepresentation `json:"protocolMappers,omitempty"` - PublicClient *bool `json:"publicClient,omitempty"` - RedirectUris *[]string `json:"redirectUris,omitempty"` - RegisteredNodes *map[string]interface{} `json:"registeredNodes,omitempty"` - RegistrationAccessToken *string `json:"registrationAccessToken,omitempty"` - RootUrl *string `json:"rootUrl,omitempty"` - Secret *string `json:"secret,omitempty"` - ServiceAccountsEnabled *bool `json:"serviceAccountsEnabled,omitempty"` - StandardFlowEnabled *bool `json:"standardFlowEnabled,omitempty"` - SurrogateAuthRequired *bool `json:"surrogateAuthRequired,omitempty"` - UseTemplateConfig *bool `json:"useTemplateConfig,omitempty"` - UseTemplateMappers *bool `json:"useTemplateMappers,omitempty"` - UseTemplateScope *bool `json:"useTemplateScope,omitempty"` - WebOrigins *[]string `json:"webOrigins,omitempty"` + Access *map[string]interface{} `json:"access,omitempty"` + AdminUrl *string `json:"adminUrl,omitempty"` + Attributes *map[string]interface{} `json:"attributes,omitempty"` + AuthorizationServicesEnabled *bool `json:"authorizationServicesEnabled,omitempty"` + AuthorizationSettings *ResourceServerRepresentation `json:"authorizationSettings,omitempty"` + BaseUrl *string `json:"baseUrl,omitempty"` + BearerOnly *bool `json:"bearerOnly,omitempty"` + ClientAuthenticatorType *string `json:"clientAuthenticatorType,omitempty"` + ClientId *string `json:"clientId,omitempty"` + ClientTemplate *string `json:"clientTemplate,omitempty"` + ConsentRequired *bool `json:"consentRequired,omitempty"` + DefaultRoles *[]string `json:"defaultRoles,omitempty"` + Description *string `json:"description,omitempty"` + DirectAccessGrantsEnabled *bool `json:"directAccessGrantsEnabled,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + FrontchannelLogout *bool `json:"frontchannelLogout,omitempty"` + FullScopeAllowed *bool `json:"fullScopeAllowed,omitempty"` + Id *string `json:"id,omitempty"` + ImplicitFlowEnabled *bool `json:"implicitFlowEnabled,omitempty"` + Name *string `json:"name,omitempty"` + NodeReRegistrationTimeout *int32 `json:"nodeReRegistrationTimeout,omitempty"` + NotBefore *int32 `json:"notBefore,omitempty"` + Protocol *string `json:"protocol,omitempty"` + ProtocolMappers *[]ProtocolMapperRepresentation `json:"protocolMappers,omitempty"` + PublicClient *bool `json:"publicClient,omitempty"` + RedirectUris *[]string `json:"redirectUris,omitempty"` + RegisteredNodes *map[string]interface{} `json:"registeredNodes,omitempty"` + RegistrationAccessToken *string `json:"registrationAccessToken,omitempty"` + RootUrl *string `json:"rootUrl,omitempty"` + Secret *string `json:"secret,omitempty"` + ServiceAccountsEnabled *bool `json:"serviceAccountsEnabled,omitempty"` + StandardFlowEnabled *bool `json:"standardFlowEnabled,omitempty"` + SurrogateAuthRequired *bool `json:"surrogateAuthRequired,omitempty"` + UseTemplateConfig *bool `json:"useTemplateConfig,omitempty"` + UseTemplateMappers *bool `json:"useTemplateMappers,omitempty"` + UseTemplateScope *bool `json:"useTemplateScope,omitempty"` + WebOrigins *[]string `json:"webOrigins,omitempty"` } type ClientTemplateRepresentation struct { From 6f567f17983b13d1e33e2ede07da2e212b8e6134 Mon Sep 17 00:00:00 2001 From: Johan Droz Date: Wed, 14 Mar 2018 18:42:14 +0100 Subject: [PATCH 2/8] Add path for attack-detection, handle errors with errors package --- Gopkg.lock | 14 +++++++++++++- attack_detection.go | 27 +++++++++++++++++++++++++++ client_test.go | 1 - client.go => keycloak_client.go | 25 +++++++++++++------------ 4 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 attack_detection.go delete mode 100644 client_test.go rename client.go => keycloak_client.go (90%) diff --git a/Gopkg.lock b/Gopkg.lock index 6d541e4..ca42fb3 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -19,6 +19,12 @@ revision = "925541529c1fa6821df4e44ce2723319eb2be768" version = "v1.0.0" +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + [[projects]] name = "github.com/pmezard/go-difflib" packages = ["difflib"] @@ -31,6 +37,12 @@ packages = [".","cacheobject"] revision = "0dec1b30a0215bb68605dfc568e8855066c9202d" +[[projects]] + name = "github.com/spf13/pflag" + packages = ["."] + revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" + version = "v1.0.0" + [[projects]] name = "github.com/stretchr/testify" packages = ["assert","require"] @@ -82,6 +94,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "5771516553a6ddbd4ae08c137169a7ca8437e019d877ae76413fbb7191e491ee" + inputs-digest = "bcd4dc5f3937d83e106e1a9999b3a7f5a9544ba495a3375c920688c9e73afce8" solver-name = "gps-cdcl" solver-version = 1 diff --git a/attack_detection.go b/attack_detection.go new file mode 100644 index 0000000..28e099e --- /dev/null +++ b/attack_detection.go @@ -0,0 +1,27 @@ +package keycloak + +import ( + "gopkg.in/h2non/gentleman.v2/plugins/url" +) + +const ( + attackDetectionPath = "/auth/admin/realms/:realm/attack-detection/brute-force/users" + attackDetectionIDPath = attackDetectionPath + "/:id" +) + +// ClearAllLoginFailures clears any user login failures for all users. This can release temporary disabled users. +func (c *Client) ClearAllLoginFailures(realmName string) error { + return c.delete(url.Path(attackDetectionPath), url.Param("realm", realmName)) +} + +// GetAttackDetectionStatus gets the status of a username in brute force detection. +func (c *Client) GetAttackDetectionStatus(realmName, userID string) (map[string]interface{}, error) { + var resp = map[string]interface{}{} + var err = c.get(&resp, url.Path(attackDetectionIDPath), url.Param("realm", realmName), url.Param("id", userID)) + return resp, err +} + +// ClearUserLoginFailures clear any user login failures for the user. This can release temporary disabled user. +func (c *Client) ClearUserLoginFailures(realmName, userID string) error { + return c.delete(url.Path(attackDetectionIDPath), url.Param("realm", realmName), url.Param("id", userID)) +} diff --git a/client_test.go b/client_test.go deleted file mode 100644 index cf172a0..0000000 --- a/client_test.go +++ /dev/null @@ -1 +0,0 @@ -package keycloak diff --git a/client.go b/keycloak_client.go similarity index 90% rename from client.go rename to keycloak_client.go index 2588180..94224dd 100644 --- a/client.go +++ b/keycloak_client.go @@ -9,6 +9,7 @@ import ( "time" oidc "github.com/coreos/go-oidc" + "github.com/pkg/errors" "gopkg.in/h2non/gentleman.v2" "gopkg.in/h2non/gentleman.v2/plugin" "gopkg.in/h2non/gentleman.v2/plugins/query" @@ -39,7 +40,7 @@ func New(config Config) (*Client, error) { var err error u, err = url.Parse(config.Addr) if err != nil { - return nil, fmt.Errorf("could not parse URL: %v", err) + return nil, errors.Wrap(err, "could not parse URL") } } @@ -59,7 +60,7 @@ func New(config Config) (*Client, error) { var issuer = fmt.Sprintf("%s/auth/realms/master", u.String()) oidcProvider, err = oidc.NewProvider(context.Background(), issuer) if err != nil { - return nil, fmt.Errorf("could not create oidc provider: %v", err) + return nil, errors.Wrap(err, "could not create oidc provider") } } @@ -88,7 +89,7 @@ func (c *Client) getToken() error { var err error resp, err = req.Do() if err != nil { - return fmt.Errorf("could not get token: %v", err) + return errors.Wrap(err, "could not get token") } } defer resp.Close() @@ -98,7 +99,7 @@ func (c *Client) getToken() error { var err error err = resp.JSON(&unmarshalledBody) if err != nil { - return fmt.Errorf("could not unmarshal response: %v", err) + return errors.Wrap(err, "could not unmarshal response") } } @@ -134,7 +135,7 @@ func (c *Client) get(data interface{}, plugins ...plugin.Plugin) error { var err error resp, err = req.Do() if err != nil { - return fmt.Errorf("could not get response: %v", err) + return errors.Wrap(err, "could not get response") } switch { @@ -143,7 +144,7 @@ func (c *Client) get(data interface{}, plugins ...plugin.Plugin) error { if err = c.verifyToken(); err != nil { var err = c.getToken() if err != nil { - return fmt.Errorf("could not get token: %v", err) + return errors.Wrap(err, "could not get token: %v") } } return c.get(data, plugins...) @@ -166,7 +167,7 @@ func (c *Client) post(plugins ...plugin.Plugin) error { var err error resp, err = req.Do() if err != nil { - return fmt.Errorf("could not get response: %v", err) + return errors.Wrap(err, "could not get response") } switch { @@ -175,7 +176,7 @@ func (c *Client) post(plugins ...plugin.Plugin) error { if err = c.verifyToken(); err != nil { var err = c.getToken() if err != nil { - return fmt.Errorf("could not get token: %v", err) + return errors.Wrap(err, "could not get token") } } return c.post(plugins...) @@ -198,7 +199,7 @@ func (c *Client) delete(plugins ...plugin.Plugin) error { var err error resp, err = req.Do() if err != nil { - return fmt.Errorf("could not get response: %v", err) + return errors.Wrap(err, "could not get response") } switch { @@ -207,7 +208,7 @@ func (c *Client) delete(plugins ...plugin.Plugin) error { if err = c.verifyToken(); err != nil { var err = c.getToken() if err != nil { - return fmt.Errorf("could not get token: %v", err) + return errors.Wrap(err, "could not get token") } } return c.delete(plugins...) @@ -230,7 +231,7 @@ func (c *Client) put(plugins ...plugin.Plugin) error { var err error resp, err = req.Do() if err != nil { - return fmt.Errorf("could not get response: %v", err) + return errors.Wrap(err, "could not get response") } switch { @@ -239,7 +240,7 @@ func (c *Client) put(plugins ...plugin.Plugin) error { if err = c.verifyToken(); err != nil { var err = c.getToken() if err != nil { - return fmt.Errorf("could not get token: %v", err) + return errors.Wrap(err, "could not get token: %v") } } return c.put(plugins...) From 4dd2d7739695ef2247c6fd014684c9a599f55ede Mon Sep 17 00:00:00 2001 From: Johan Droz Date: Wed, 14 Mar 2018 21:28:34 +0100 Subject: [PATCH 3/8] Add template for full Keycloak REST API implementation --- authentication_management.go | 88 +++++++++++++++++++ ...test.go => client_attribute_certificate.go | 0 client_initial_access.go | 1 + client_registration_policy.go | 1 + client_role_mappings.go | 1 + client_templates.go | 1 + component.go | 1 + groups.go | 1 + identity_providers.go | 1 + key.go | 1 + realms_admin.go | 1 + role_mapper.go | 1 + roles.go | 1 + root.go | 1 + scope_mappings.go | 1 + user_storage_provider.go | 1 + user.go => users.go | 0 17 files changed, 102 insertions(+) create mode 100644 authentication_management.go rename user_test.go => client_attribute_certificate.go (100%) create mode 100644 client_initial_access.go create mode 100644 client_registration_policy.go create mode 100644 client_role_mappings.go create mode 100644 client_templates.go create mode 100644 component.go create mode 100644 groups.go create mode 100644 identity_providers.go create mode 100644 key.go create mode 100644 realms_admin.go create mode 100644 role_mapper.go create mode 100644 roles.go create mode 100644 root.go create mode 100644 scope_mappings.go create mode 100644 user_storage_provider.go rename user.go => users.go (100%) diff --git a/authentication_management.go b/authentication_management.go new file mode 100644 index 0000000..453bd41 --- /dev/null +++ b/authentication_management.go @@ -0,0 +1,88 @@ +package keycloak + +// Get authenticator providers Returns a list of authenticator providers. +// GET /{realm}/authentication/authenticator-providers + +// Get client authenticator providers Returns a list of client authenticator providers. +// GET /{realm}/authentication/client-authenticator-providers + +// Get authenticator provider’s configuration description +// GET /{realm}/authentication/config-description/{providerId} + +// Get authenticator configuration +// GET /{realm}/authentication/config/{id} + +// Update authenticator configuration +// PUT /{realm}/authentication/config/{id} + +// Delete authenticator configuration +// DELETE /{realm}/authentication/config/{id} + +// Add new authentication execution +// POST /{realm}/authentication/executions + +// Delete execution +// DELETE /{realm}/authentication/executions/{executionId} + +// Update execution with new configuration +// POST /{realm}/authentication/executions/{executionId}/config + +// Lower execution’s priority +// POST /{realm}/authentication/executions/{executionId}/lower-priority + +// Raise execution’s priority +// POST /{realm}/authentication/executions/{executionId}/raise-priority + +// Create a new authentication flow +// POST /{realm}/authentication/flows + +// Get authentication flows Returns a list of authentication flows. +// GET /{realm}/authentication/flows + +// Copy existing authentication flow under a new name The new name is given as 'newName' attribute of the passed JSON object +// POST /{realm}/authentication/flows/{flowAlias}/copy + +// Get authentication executions for a flow +// GET /{realm}/authentication/flows/{flowAlias}/executions + +// Update authentication executions of a flow +// PUT /{realm}/authentication/flows/{flowAlias}/executions + +// Add new authentication execution to a flow +// POST /{realm}/authentication/flows/{flowAlias}/executions/execution + +// Add new flow with new execution to existing flow +// POST /{realm}/authentication/flows/{flowAlias}/executions/flow + +// Get authentication flow for id +// GET /{realm}/authentication/flows/{id} + +// Delete an authentication flow +// DELETE /{realm}/authentication/flows/{id} + +// Get form action providers Returns a list of form action providers. +// GET /{realm}/authentication/form-action-providers + +// Get form providers Returns a list of form providers. +// GET /{realm}/authentication/form-providers + +// Get configuration descriptions for all clients +// GET /{realm}/authentication/per-client-config-description + +// Register a new required actions +// POST /{realm}/authentication/register-required-action + +// Get required actions Returns a list of required actions. +// GET /{realm}/authentication/required-actions + +// Get required action for alias +// GET /{realm}/authentication/required-actions/{alias} + +// Update required action +// PUT /{realm}/authentication/required-actions/{alias} + +// Delete required action +// DELETE /{realm}/authentication/required-actions/{alias} + +// Get unregistered required actions Returns a list of unregistered required actions. +// GET /{realm}/authentication/unregistered-required-actions diff --git a/user_test.go b/client_attribute_certificate.go similarity index 100% rename from user_test.go rename to client_attribute_certificate.go diff --git a/client_initial_access.go b/client_initial_access.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/client_initial_access.go @@ -0,0 +1 @@ +package keycloak diff --git a/client_registration_policy.go b/client_registration_policy.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/client_registration_policy.go @@ -0,0 +1 @@ +package keycloak diff --git a/client_role_mappings.go b/client_role_mappings.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/client_role_mappings.go @@ -0,0 +1 @@ +package keycloak diff --git a/client_templates.go b/client_templates.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/client_templates.go @@ -0,0 +1 @@ +package keycloak diff --git a/component.go b/component.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/component.go @@ -0,0 +1 @@ +package keycloak diff --git a/groups.go b/groups.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/groups.go @@ -0,0 +1 @@ +package keycloak diff --git a/identity_providers.go b/identity_providers.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/identity_providers.go @@ -0,0 +1 @@ +package keycloak diff --git a/key.go b/key.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/key.go @@ -0,0 +1 @@ +package keycloak diff --git a/realms_admin.go b/realms_admin.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/realms_admin.go @@ -0,0 +1 @@ +package keycloak diff --git a/role_mapper.go b/role_mapper.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/role_mapper.go @@ -0,0 +1 @@ +package keycloak diff --git a/roles.go b/roles.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/roles.go @@ -0,0 +1 @@ +package keycloak diff --git a/root.go b/root.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/root.go @@ -0,0 +1 @@ +package keycloak diff --git a/scope_mappings.go b/scope_mappings.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/scope_mappings.go @@ -0,0 +1 @@ +package keycloak diff --git a/user_storage_provider.go b/user_storage_provider.go new file mode 100644 index 0000000..cf172a0 --- /dev/null +++ b/user_storage_provider.go @@ -0,0 +1 @@ +package keycloak diff --git a/user.go b/users.go similarity index 100% rename from user.go rename to users.go From 05b77544d7981f73d9a1477d6869ed058554c523 Mon Sep 17 00:00:00 2001 From: Johan Droz Date: Thu, 15 Mar 2018 18:48:56 +0100 Subject: [PATCH 4/8] Add some routes --- authentication_management.go | 44 +++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/authentication_management.go b/authentication_management.go index 453bd41..13b9444 100644 --- a/authentication_management.go +++ b/authentication_management.go @@ -1,16 +1,38 @@ package keycloak -// Get authenticator providers Returns a list of authenticator providers. -// GET /{realm}/authentication/authenticator-providers - -// Get client authenticator providers Returns a list of client authenticator providers. -// GET /{realm}/authentication/client-authenticator-providers - -// Get authenticator provider’s configuration description -// GET /{realm}/authentication/config-description/{providerId} - -// Get authenticator configuration -// GET /{realm}/authentication/config/{id} +import "gopkg.in/h2non/gentleman.v2/plugins/url" + +const ( + authenticationManagementPath = "/auth/admin/realms/:realm/authentication" +) + +// GetAuthenticatorProviders returns a list of authenticator providers. +func (c *Client) GetAuthenticatorProviders(realmName string) ([]map[string]interface{}, error) { + var resp = []map[string]interface{}{} + var err = c.get(&resp, url.Path(authenticationManagementPath+"/authenticator-providers"), url.Param("realm", realmName)) + return resp, err +} + +// GetClientAuthenticatorProviders returns a list of client authenticator providers. +func (c *Client) GetClientAuthenticatorProviders(realmName string) ([]map[string]interface{}, error) { + var resp = []map[string]interface{}{} + var err = c.get(&resp, url.Path(authenticationManagementPath+"client-authenticator-providers"), url.Param("realm", realmName)) + return resp, err +} + +// GetAuthenticatorProviderConfig returns the authenticator provider’s configuration description. +func (c *Client) GetAuthenticatorProviderConfig(realmName, providerID string) (AuthenticatorConfigInfoRepresentation, error) { + var resp = AuthenticatorConfigInfoRepresentation{} + var err = c.get(&resp, url.Path(authenticationManagementPath+"config-description/:providerID"), url.Param("realm", realmName), url.Param("providerID", providerID)) + return resp, err +} + +// GetAuthenticatorConfig returns the authenticator configuration. +func (c *Client) GetAuthenticatorConfig(realmName, configID string) (AuthenticatorConfigRepresentation, error) { + var resp = AuthenticatorConfigRepresentation{} + var err = c.get(&resp, url.Path(authenticationManagementPath+"config/:id"), url.Param("realm", realmName), url.Param("id", configID)) + return resp, err +} // Update authenticator configuration // PUT /{realm}/authentication/config/{id} From de225f0ef9a75f16480475dc9581249f4358d599 Mon Sep 17 00:00:00 2001 From: Johan Droz Date: Fri, 16 Mar 2018 22:27:46 +0100 Subject: [PATCH 5/8] Add authentication managements routes --- authentication_management.go | 187 +++++++++++++++++++++++++---------- 1 file changed, 133 insertions(+), 54 deletions(-) diff --git a/authentication_management.go b/authentication_management.go index 13b9444..25fca93 100644 --- a/authentication_management.go +++ b/authentication_management.go @@ -1,6 +1,9 @@ package keycloak -import "gopkg.in/h2non/gentleman.v2/plugins/url" +import ( + "gopkg.in/h2non/gentleman.v2/plugins/body" + "gopkg.in/h2non/gentleman.v2/plugins/url" +) const ( authenticationManagementPath = "/auth/admin/realms/:realm/authentication" @@ -16,95 +19,171 @@ func (c *Client) GetAuthenticatorProviders(realmName string) ([]map[string]inter // GetClientAuthenticatorProviders returns a list of client authenticator providers. func (c *Client) GetClientAuthenticatorProviders(realmName string) ([]map[string]interface{}, error) { var resp = []map[string]interface{}{} - var err = c.get(&resp, url.Path(authenticationManagementPath+"client-authenticator-providers"), url.Param("realm", realmName)) + var err = c.get(&resp, url.Path(authenticationManagementPath+"/client-authenticator-providers"), url.Param("realm", realmName)) return resp, err } // GetAuthenticatorProviderConfig returns the authenticator provider’s configuration description. func (c *Client) GetAuthenticatorProviderConfig(realmName, providerID string) (AuthenticatorConfigInfoRepresentation, error) { var resp = AuthenticatorConfigInfoRepresentation{} - var err = c.get(&resp, url.Path(authenticationManagementPath+"config-description/:providerID"), url.Param("realm", realmName), url.Param("providerID", providerID)) + var err = c.get(&resp, url.Path(authenticationManagementPath+"/config-description/:providerID"), url.Param("realm", realmName), url.Param("providerID", providerID)) return resp, err } // GetAuthenticatorConfig returns the authenticator configuration. func (c *Client) GetAuthenticatorConfig(realmName, configID string) (AuthenticatorConfigRepresentation, error) { var resp = AuthenticatorConfigRepresentation{} - var err = c.get(&resp, url.Path(authenticationManagementPath+"config/:id"), url.Param("realm", realmName), url.Param("id", configID)) + var err = c.get(&resp, url.Path(authenticationManagementPath+"/config/:id"), url.Param("realm", realmName), url.Param("id", configID)) return resp, err } -// Update authenticator configuration -// PUT /{realm}/authentication/config/{id} +// UpdateAuthenticatorConfig updates the authenticator configuration. +func (c *Client) UpdateAuthenticatorConfig(realmName, configID string, config AuthenticatorConfigRepresentation) error { + return c.put(url.Path(authenticationManagementPath+"/config/:id"), url.Param("realm", realmName), url.Param("id", configID), body.JSON(config)) +} -// Delete authenticator configuration -// DELETE /{realm}/authentication/config/{id} +// DeleteAuthenticatorConfig deletes the authenticator configuration. +func (c *Client) DeleteAuthenticatorConfig(realmName, configID string) error { + return c.delete(url.Path(authenticationManagementPath+"/config/:id"), url.Param("realm", realmName), url.Param("id", configID)) +} -// Add new authentication execution -// POST /{realm}/authentication/executions +// CreateAuthenticationExecution add new authentication execution +func (c *Client) CreateAuthenticationExecution(realmName string, authExec AuthenticationExecutionRepresentation) error { + return c.post(url.Path(authenticationManagementPath+"/executions"), url.Param("realm", realmName), body.JSON(authExec)) +} -// Delete execution -// DELETE /{realm}/authentication/executions/{executionId} +// DeleteAuthenticationExecution deletes the execution. +func (c *Client) DeleteAuthenticationExecution(realmName, executionID string) error { + return c.delete(url.Path(authenticationManagementPath+"/executions/:id"), url.Param("realm", realmName), url.Param("id", executionID)) +} -// Update execution with new configuration -// POST /{realm}/authentication/executions/{executionId}/config +// UpdateAuthenticationExecution update execution with new configuration. +func (c *Client) UpdateAuthenticationExecution(realmName, executionID string, authConfig AuthenticatorConfigRepresentation) error { + return c.post(url.Path(authenticationManagementPath+"/executions/:id/config"), url.Param("realm", realmName), url.Param("id", executionID), body.JSON(authConfig)) +} -// Lower execution’s priority -// POST /{realm}/authentication/executions/{executionId}/lower-priority +// LowerExecutionPriority lowers the execution’s priority. +func (c *Client) LowerExecutionPriority(realmName, executionID string) error { + return c.post(url.Path(authenticationManagementPath+"/executions/:id/lower-priority"), url.Param("realm", realmName), url.Param("id", executionID)) +} -// Raise execution’s priority -// POST /{realm}/authentication/executions/{executionId}/raise-priority +// RaiseExecutionPriority raise the execution’s priority. +func (c *Client) RaiseExecutionPriority(realmName, executionID string) error { + return c.post(url.Path(authenticationManagementPath+"/executions/:id/raise-priority"), url.Param("realm", realmName), url.Param("id", executionID)) +} -// Create a new authentication flow -// POST /{realm}/authentication/flows +// CreateAuthenticationFlow creates a new authentication flow. +func (c *Client) CreateAuthenticationFlow(realmName string, authFlow AuthenticationFlowRepresentation) error { + return c.post(url.Path(authenticationManagementPath+"/flows"), url.Param("realm", realmName), body.JSON(authFlow)) +} -// Get authentication flows Returns a list of authentication flows. -// GET /{realm}/authentication/flows +// GetAuthenticationFlows returns a list of authentication flows. +func (c *Client) GetAuthenticationFlows(realmName string) ([]AuthenticationFlowRepresentation, error) { + var resp = []AuthenticationFlowRepresentation{} + var err = c.get(&resp, url.Path(authenticationManagementPath+"/flows"), url.Param("realm", realmName)) + return resp, err +} -// Copy existing authentication flow under a new name The new name is given as 'newName' attribute of the passed JSON object -// POST /{realm}/authentication/flows/{flowAlias}/copy +// CopyExistingAuthenticationFlow copy the existing authentication flow under a new name. +// 'flowAlias' is the name of the existing authentication flow, +// 'newName' is the new name of the authentication flow. +func (c *Client) CopyExistingAuthenticationFlow(realmName, flowAlias, newName string) error { + var m = map[string]string{"newName": newName} + return c.post(url.Path(authenticationManagementPath+"/flows/:flowAlias/copy"), url.Param("realm", realmName), url.Param("flowAlias", flowAlias), body.JSON(m)) +} -// Get authentication executions for a flow -// GET /{realm}/authentication/flows/{flowAlias}/executions +// GetAuthenticationExecutionForFlow returns the authentication executions for a flow. +func (c *Client) GetAuthenticationExecutionForFlow(realmName, flowAlias string) (AuthenticationExecutionInfoRepresentation, error) { + var resp = AuthenticationExecutionInfoRepresentation{} + var err = c.get(&resp, url.Path(authenticationManagementPath+"/flows/:flowAlias/executions"), url.Param("realm", realmName), url.Param("flowAlias", flowAlias)) + return resp, err +} -// Update authentication executions of a flow -// PUT /{realm}/authentication/flows/{flowAlias}/executions +// UpdateAuthenticationExecutionForFlow updates the authentication executions of a flow. +func (c *Client) UpdateAuthenticationExecutionForFlow(realmName, flowAlias string, authExecInfo AuthenticationExecutionInfoRepresentation) error { + return c.put(url.Path(authenticationManagementPath+"/flows/:flowAlias/executions"), url.Param("realm", realmName), url.Param("flowAlias", flowAlias), body.JSON(authExecInfo)) +} -// Add new authentication execution to a flow -// POST /{realm}/authentication/flows/{flowAlias}/executions/execution +// CreateAuthenticationExecutionForFlow add a new authentication execution to a flow. +// 'flowAlias' is the alias of the parent flow. +func (c *Client) CreateAuthenticationExecutionForFlow(realmName, flowAlias, provider string) error { + var m = map[string]string{"provider": provider} + return c.post(url.Path(authenticationManagementPath+"/flows/:flowAlias/executions/execution"), url.Param("realm", realmName), url.Param("flowAlias", flowAlias), body.JSON(m)) +} -// Add new flow with new execution to existing flow -// POST /{realm}/authentication/flows/{flowAlias}/executions/flow +// CreateFlowWithExecutionForExistingFlow add a new flow with a new execution to an existing flow. +// 'flowAlias' is the alias of the parent authentication flow. +func (c *Client) CreateFlowWithExecutionForExistingFlow(realmName, flowAlias, alias, flowType, provider, description string) error { + var m = map[string]string{"alias": alias, "type": flowType, "provider": provider, "description": description} + return c.post(url.Path(authenticationManagementPath+"/flows/:flowAlias/executions/flow"), url.Param("realm", realmName), url.Param("flowAlias", flowAlias), body.JSON(m)) +} -// Get authentication flow for id -// GET /{realm}/authentication/flows/{id} +// GetAuthenticationFlow gets the authentication flow for id. +func (c *Client) GetAuthenticationFlow(realmName, flowID string) (AuthenticationFlowRepresentation, error) { + var resp = AuthenticationFlowRepresentation{} + var err = c.get(&resp, url.Path(authenticationManagementPath+"/flows/:id"), url.Param("realm", realmName), url.Param("id", flowID)) + return resp, err +} -// Delete an authentication flow -// DELETE /{realm}/authentication/flows/{id} +// DeleteAuthenticationFlow deletes an authentication flow. +func (c *Client) DeleteAuthenticationFlow(realmName, flowID string) error { + return c.delete(url.Path(authenticationManagementPath+"/flows/:id"), url.Param("realm", realmName), url.Param("id", flowID)) +} -// Get form action providers Returns a list of form action providers. -// GET /{realm}/authentication/form-action-providers +// GetFormActionProviders returns a list of form action providers. +func (c *Client) GetFormActionProviders(realmName string) ([]map[string]interface{}, error) { + var resp = []map[string]interface{}{} + var err = c.get(&resp, url.Path(authenticationManagementPath+"/form-action-providers"), url.Param("realm", realmName)) + return resp, err +} -// Get form providers Returns a list of form providers. -// GET /{realm}/authentication/form-providers +// GetFormProviders returns a list of form providers. +func (c *Client) GetFormProviders(realmName string) ([]map[string]interface{}, error) { + var resp = []map[string]interface{}{} + var err = c.get(&resp, url.Path(authenticationManagementPath+"/form-providers"), url.Param("realm", realmName)) + return resp, err +} -// Get configuration descriptions for all clients -// GET /{realm}/authentication/per-client-config-description +// GetConfigDescriptionForClients returns the configuration descriptions for all clients. +func (c *Client) GetConfigDescriptionForClients(realmName string) (map[string]interface{}, error) { + var resp = map[string]interface{}{} + var err = c.get(&resp, url.Path(authenticationManagementPath+"/per-client-config-description"), url.Param("realm", realmName)) + return resp, err +} -// Register a new required actions -// POST /{realm}/authentication/register-required-action +// RegisterRequiredAction register a new required action. +func (c *Client) RegisterRequiredAction(realmName, providerID, name string) error { + var m = map[string]string{"providerId": providerID, "name": name} + return c.post(url.Path(authenticationManagementPath+"/register-required-action"), url.Param("realm", realmName), body.JSON(m)) +} -// Get required actions Returns a list of required actions. -// GET /{realm}/authentication/required-actions +// GetRequiredActions returns a list of required actions. +func (c *Client) GetRequiredActions(realmName string) ([]RequiredActionProviderRepresentation, error) { + var resp = []RequiredActionProviderRepresentation{} + var err = c.get(&resp, url.Path(authenticationManagementPath+"/required-actions"), url.Param("realm", realmName)) + return resp, err +} -// Get required action for alias -// GET /{realm}/authentication/required-actions/{alias} +// GetRequiredAction returns the required action for the alias. +func (c *Client) GetRequiredAction(realmName, actionAlias string) (RequiredActionProviderRepresentation, error) { + var resp = RequiredActionProviderRepresentation{} + var err = c.get(&resp, url.Path(authenticationManagementPath+"/required-actions/:alias"), url.Param("realm", realmName), url.Param("alias", actionAlias)) + return resp, err +} -// Update required action -// PUT /{realm}/authentication/required-actions/{alias} +// UpdateRequiredAction updates the required action. +func (c *Client) UpdateRequiredAction(realmName, actionAlias string, action RequiredActionProviderRepresentation) error { + return c.put(url.Path(authenticationManagementPath+"/required-actions/:alias"), url.Param("realm", realmName), url.Param("alias", actionAlias), body.JSON(action)) +} -// Delete required action -// DELETE /{realm}/authentication/required-actions/{alias} +// DeleteRequiredAction deletes the required action. +func (c *Client) DeleteRequiredAction(realmName, actionAlias string) error { + return c.delete(url.Path(authenticationManagementPath+"/required-actions/:alias"), url.Param("realm", realmName), url.Param("alias", actionAlias)) +} -// Get unregistered required actions Returns a list of unregistered required actions. -// GET /{realm}/authentication/unregistered-required-actions +// GetUnregisteredRequiredActions returns a list of unregistered required actions. +func (c *Client) GetUnregisteredRequiredActions(realmName string) ([]map[string]interface{}, error) { + var resp = []map[string]interface{}{} + var err = c.get(&resp, url.Path(authenticationManagementPath+"/unregistered-required-actions"), url.Param("realm", realmName)) + return resp, err +} From e334982485e670886b57141c3bda2b3fd633fe0c Mon Sep 17 00:00:00 2001 From: Johan Droz Date: Fri, 16 Mar 2018 22:55:10 +0100 Subject: [PATCH 6/8] Add client attribute certificate, this is temporary work, the keycloak_client's post method must be modified --- client_attribute_certificate.go | 53 +++++++++++++++++++++++++++++++++ users.go | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/client_attribute_certificate.go b/client_attribute_certificate.go index cf172a0..ff79e60 100644 --- a/client_attribute_certificate.go +++ b/client_attribute_certificate.go @@ -1 +1,54 @@ package keycloak + +import ( + "bytes" + + "gopkg.in/h2non/gentleman.v2/plugins/body" + "gopkg.in/h2non/gentleman.v2/plugins/url" +) + +const ( + clientAttrCertPath = "/auth/admin/realms/:realm/clients/:id/certificates/:attr" +) + +// GetKeyInfo returns the key info. idClient is the id of client (not client-id). +func (c *Client) GetKeyInfo(realmName, idClient, attr string) (CertificateRepresentation, error) { + var resp = CertificateRepresentation{} + var err = c.get(&resp, url.Path(clientAttrCertPath), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr)) + return resp, err +} + +// GetKeyStore returns a keystore file for the client, containing private key and public certificate. idClient is the id of client (not client-id). +func (c *Client) GetKeyStore(realmName, idClient, attr string, keyStoreConfig KeyStoreConfig) ([]byte, error) { + var resp = []byte{} + var err = c.post(url.Path(clientAttrCertPath+"/download"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.JSON(keyStoreConfig)) + return respfrompost, err +} + +// GenerateCertificate generates a new certificate with new key pair. idClient is the id of client (not client-id). +func (c *Client) GenerateCertificate(realmName, idClient, attr string) (CertificateRepresentation, error) { + var resp = CertificateRepresentation{} + var err = c.post(url.Path(clientAttrCertPath+"/generate"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr)) + return respfrompost, err +} + +// GenerateKeyPairAndCertificate generates a keypair and certificate and serves the private key in a specified keystore format. +func (c *Client) GenerateKeyPairAndCertificate(realmName, idClient, attr string, keyStoreConfig KeyStoreConfig) ([]byte, error) { + var resp = []byte{} + var err = c.post(url.Path(clientAttrCertPath+"/generate-and-download"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.JSON(keyStoreConfig)) + return respfrompost, err +} + +// UploadCertificatePrivateKey uploads a certificate and eventually a private key. +func (c *Client) UploadCertificatePrivateKey(realmName, idClient, attr string, file []byte) (CertificateRepresentation, error) { + var resp = CertificateRepresentation{} + var err = c.post(url.Path(clientAttrCertPath+"/upload"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.Reader(bytes.NewReader(file))) + return respfrompost, err +} + +// UploadCertificate uploads only a certificate, not the private key. +func (c *Client) UploadCertificate(realmName, idClient, attr string, file []byte) (CertificateRepresentation, error) { + var resp = CertificateRepresentation{} + var err = c.post(url.Path(clientAttrCertPath+"/upload-certificate"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.Reader(bytes.NewReader(file))) + return respfrompost, err +} diff --git a/users.go b/users.go index 77cd18e..3738cf6 100644 --- a/users.go +++ b/users.go @@ -47,7 +47,7 @@ func (c *Client) GetUser(realmName, userID string) (UserRepresentation, error) { return resp, err } -// UpdateUser update the user. +// UpdateUser updates the user. func (c *Client) UpdateUser(realmName, userID string, user UserRepresentation) error { return c.put(url.Path(userIDPath), url.Param("realm", realmName), url.Param("id", userID), body.JSON(user)) } From 7ffcaeb28a7f21ef1bac29cb4e3ad0fe2118ba43 Mon Sep 17 00:00:00 2001 From: Johan Droz Date: Sun, 18 Mar 2018 15:40:11 +0100 Subject: [PATCH 7/8] tmp --- client_initial_access.go | 28 ++++++++++++++++++++++++++++ client_registration_policy.go | 13 +++++++++++++ client_role_mappings.go | 26 ++++++++++++++++++++++++++ keycloak_client.go | 13 ++++++++++--- users.go | 4 ++-- 5 files changed, 79 insertions(+), 5 deletions(-) diff --git a/client_initial_access.go b/client_initial_access.go index cf172a0..39fa52d 100644 --- a/client_initial_access.go +++ b/client_initial_access.go @@ -1 +1,29 @@ package keycloak + +import ( + "gopkg.in/h2non/gentleman.v2/plugins/body" + "gopkg.in/h2non/gentleman.v2/plugins/url" +) + +const ( + clientInitialAccessPath = "/auth/admin/realms/:realm/clients-initial-access" +) + +// CreateClientInitialAccess creates a new initial access token. +func (c *Client) CreateClientInitialAccess(realmName string, access ClientInitialAccessCreatePresentation) (ClientInitialAccessPresentation, error) { + var resp = ClientInitialAccessPresentation{} + var err = c.post(url.Path(clientInitialAccessPath), url.Param("realm", realmName), body.JSON(access)) + return respasf, err +} + +// GetClientInitialAccess returns a list of clients initial access. +func (c *Client) GetClientInitialAccess(realmName string) ([]ClientInitialAccessPresentation, error) { + var resp = []ClientInitialAccessPresentation{} + var err = c.get(&resp, url.Path(clientInitialAccessPath), url.Param("realm", realmName)) + return resp, err +} + +// DeleteClientInitialAccess deletes the client initial access. +func (c *Client) DeleteClientInitialAccess(realmName, accessID string) error { + return c.delete(url.Path(clientInitialAccessPath+"/:id"), url.Param("realm", realmName), url.Param("id", accessID)) +} diff --git a/client_registration_policy.go b/client_registration_policy.go index cf172a0..a1b6d39 100644 --- a/client_registration_policy.go +++ b/client_registration_policy.go @@ -1 +1,14 @@ package keycloak + +import "gopkg.in/h2non/gentleman.v2/plugins/url" + +const ( + clientRegistrationPolicyPath = "/auth/admin/realms/:realm/client-registration-policy/providers" +) + +// GetClientRegistrationPolicy is the base path to retrieve providers with the configProperties properly filled. +func (c *Client) GetClientRegistrationPolicy(realmName, configID string) ([]ComponentTypeRepresentation, error) { + var resp = []ComponentTypeRepresentation{} + var err = c.get(&resp, url.Path(clientRegistrationPolicyPath), url.Param("realm", realmName)) + return resp, err +} diff --git a/client_role_mappings.go b/client_role_mappings.go index cf172a0..0aeb79e 100644 --- a/client_role_mappings.go +++ b/client_role_mappings.go @@ -1 +1,27 @@ package keycloak + +import ( + "gopkg.in/h2non/gentleman.v2/plugins/body" + "gopkg.in/h2non/gentleman.v2/plugins/url" +) + +const ( + clientRoleMappingPath = "/auth/admin/realms/:realm/groups/:id/role-mappings/clients/:client" +) + +// CreateClientsRoleMapping add client-level roles to the user role mapping. +func (c *Client) CreateClientsRoleMapping(realmName, groupID, clientID string, roles []RoleRepresentation) error { + return c.post(url.Path(clientRoleMappingPath), url.Param("realm", realmName), url.Param("id", groupID), url.Param("client", clientID), body.JSON(roles)) +} + +// GetClientsRoleMapping gets client-level role mappings for the user, and the app. +func (c *Client) GetClientsRoleMapping(realmName, groupID, clientID string) ([]RoleRepresentation, error) { + var resp = []RoleRepresentation{} + var err = c.get(&resp, url.Path(clientRoleMappingPath), url.Param("realm", realmName), url.Param("id", groupID), url.Param("client", clientID)) + return resp, err +} + +// DeleteClientsRoleMapping deletes client-level roles from user role mapping. +func (c *Client) DeleteClientsRoleMapping(realmName, groupID, clientID string) error { + return c.delete(url.Path(clientRoleMappingPath), url.Param("realm", realmName), url.Param("id", groupID), url.Param("client", clientID)) +} diff --git a/keycloak_client.go b/keycloak_client.go index 94224dd..644880f 100644 --- a/keycloak_client.go +++ b/keycloak_client.go @@ -2,7 +2,6 @@ package keycloak import ( "context" - "encoding/json" "fmt" "net/http" "net/url" @@ -128,6 +127,7 @@ func (c *Client) verifyToken() error { // get is a HTTP get method. func (c *Client) get(data interface{}, plugins ...plugin.Plugin) error { var req = c.httpClient.Get() + req = applyPlugins(req, c.accessToken, plugins...) var resp *gentleman.Response @@ -151,7 +151,15 @@ func (c *Client) get(data interface{}, plugins ...plugin.Plugin) error { case resp.StatusCode >= 400: return fmt.Errorf("invalid status code: '%v': %v", resp.RawResponse.Status, string(resp.Bytes())) case resp.StatusCode >= 200: - return json.Unmarshal(resp.Bytes(), data) + switch resp.Header.Get("Content-Type") { + case "application/json": + return resp.JSON(data) + case "application/octet-stream": + data = resp.Bytes() + return nil + default: + return fmt.Errorf("unkown http content-type: %v", resp.Header.Get("Content-Type")) + } default: return fmt.Errorf("unknown response status code: %v", resp.StatusCode) } @@ -161,7 +169,6 @@ func (c *Client) get(data interface{}, plugins ...plugin.Plugin) error { func (c *Client) post(plugins ...plugin.Plugin) error { var req = c.httpClient.Post() req = applyPlugins(req, c.accessToken, plugins...) - var resp *gentleman.Response { var err error diff --git a/users.go b/users.go index 3738cf6..36143f6 100644 --- a/users.go +++ b/users.go @@ -29,8 +29,8 @@ func (c *Client) GetUsers(realmName string, paramKV ...string) ([]UserRepresenta } // CreateUser creates the user from its UserRepresentation. The username must be unique. -func (c *Client) CreateUser(realm string, user UserRepresentation) error { - return c.post(url.Path(userPath), url.Param("realm", realm), body.JSON(user)) +func (c *Client) CreateUser(realmName string, user UserRepresentation) error { + return c.post(url.Path(userPath), url.Param("realm", realmName), body.JSON(user)) } // CountUsers returns the number of users in the realm. From e7e89f6a4f315cb1000841c1379089c74a5c7a85 Mon Sep 17 00:00:00 2001 From: Johan Droz Date: Fri, 13 Apr 2018 07:46:46 +0200 Subject: [PATCH 8/8] tmp --- authentication_management.go | 12 +++++----- client_attribute_certificate.go | 20 ++++++++--------- client_initial_access.go | 4 ++-- client_role_mappings.go | 2 +- clients_test.go | 34 +++++++++++++++------------- keycloak_client.go | 14 +++++++++--- keycloak_client_test.go | 40 +++++++++++++++++++++++++++++++++ realm.go | 2 +- users.go | 2 +- users_test.go | 23 +++++++++++++++++++ 10 files changed, 114 insertions(+), 39 deletions(-) create mode 100644 keycloak_client_test.go create mode 100644 users_test.go diff --git a/authentication_management.go b/authentication_management.go index 25fca93..c13e4f8 100644 --- a/authentication_management.go +++ b/authentication_management.go @@ -49,7 +49,7 @@ func (c *Client) DeleteAuthenticatorConfig(realmName, configID string) error { // CreateAuthenticationExecution add new authentication execution func (c *Client) CreateAuthenticationExecution(realmName string, authExec AuthenticationExecutionRepresentation) error { - return c.post(url.Path(authenticationManagementPath+"/executions"), url.Param("realm", realmName), body.JSON(authExec)) + return c.post(nil, url.Path(authenticationManagementPath+"/executions"), url.Param("realm", realmName), body.JSON(authExec)) } // DeleteAuthenticationExecution deletes the execution. @@ -59,22 +59,22 @@ func (c *Client) DeleteAuthenticationExecution(realmName, executionID string) er // UpdateAuthenticationExecution update execution with new configuration. func (c *Client) UpdateAuthenticationExecution(realmName, executionID string, authConfig AuthenticatorConfigRepresentation) error { - return c.post(url.Path(authenticationManagementPath+"/executions/:id/config"), url.Param("realm", realmName), url.Param("id", executionID), body.JSON(authConfig)) + return c.post(nil, url.Path(authenticationManagementPath+"/executions/:id/config"), url.Param("realm", realmName), url.Param("id", executionID), body.JSON(authConfig)) } // LowerExecutionPriority lowers the execution’s priority. func (c *Client) LowerExecutionPriority(realmName, executionID string) error { - return c.post(url.Path(authenticationManagementPath+"/executions/:id/lower-priority"), url.Param("realm", realmName), url.Param("id", executionID)) + return c.post(nil, url.Path(authenticationManagementPath+"/executions/:id/lower-priority"), url.Param("realm", realmName), url.Param("id", executionID)) } // RaiseExecutionPriority raise the execution’s priority. func (c *Client) RaiseExecutionPriority(realmName, executionID string) error { - return c.post(url.Path(authenticationManagementPath+"/executions/:id/raise-priority"), url.Param("realm", realmName), url.Param("id", executionID)) + return c.post(nil, url.Path(authenticationManagementPath+"/executions/:id/raise-priority"), url.Param("realm", realmName), url.Param("id", executionID)) } // CreateAuthenticationFlow creates a new authentication flow. func (c *Client) CreateAuthenticationFlow(realmName string, authFlow AuthenticationFlowRepresentation) error { - return c.post(url.Path(authenticationManagementPath+"/flows"), url.Param("realm", realmName), body.JSON(authFlow)) + return c.post(nil, url.Path(authenticationManagementPath+"/flows"), url.Param("realm", realmName), body.JSON(authFlow)) } // GetAuthenticationFlows returns a list of authentication flows. @@ -89,7 +89,7 @@ func (c *Client) GetAuthenticationFlows(realmName string) ([]AuthenticationFlowR // 'newName' is the new name of the authentication flow. func (c *Client) CopyExistingAuthenticationFlow(realmName, flowAlias, newName string) error { var m = map[string]string{"newName": newName} - return c.post(url.Path(authenticationManagementPath+"/flows/:flowAlias/copy"), url.Param("realm", realmName), url.Param("flowAlias", flowAlias), body.JSON(m)) + return c.post(nil, url.Path(authenticationManagementPath+"/flows/:flowAlias/copy"), url.Param("realm", realmName), url.Param("flowAlias", flowAlias), body.JSON(m)) } // GetAuthenticationExecutionForFlow returns the authentication executions for a flow. diff --git a/client_attribute_certificate.go b/client_attribute_certificate.go index ff79e60..e6b166f 100644 --- a/client_attribute_certificate.go +++ b/client_attribute_certificate.go @@ -21,34 +21,34 @@ func (c *Client) GetKeyInfo(realmName, idClient, attr string) (CertificateRepres // GetKeyStore returns a keystore file for the client, containing private key and public certificate. idClient is the id of client (not client-id). func (c *Client) GetKeyStore(realmName, idClient, attr string, keyStoreConfig KeyStoreConfig) ([]byte, error) { var resp = []byte{} - var err = c.post(url.Path(clientAttrCertPath+"/download"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.JSON(keyStoreConfig)) - return respfrompost, err + var err = c.post(&resp, url.Path(clientAttrCertPath+"/download"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.JSON(keyStoreConfig)) + return resp, err } // GenerateCertificate generates a new certificate with new key pair. idClient is the id of client (not client-id). func (c *Client) GenerateCertificate(realmName, idClient, attr string) (CertificateRepresentation, error) { var resp = CertificateRepresentation{} - var err = c.post(url.Path(clientAttrCertPath+"/generate"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr)) - return respfrompost, err + var err = c.post(&resp, url.Path(clientAttrCertPath+"/generate"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr)) + return resp, err } // GenerateKeyPairAndCertificate generates a keypair and certificate and serves the private key in a specified keystore format. func (c *Client) GenerateKeyPairAndCertificate(realmName, idClient, attr string, keyStoreConfig KeyStoreConfig) ([]byte, error) { var resp = []byte{} - var err = c.post(url.Path(clientAttrCertPath+"/generate-and-download"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.JSON(keyStoreConfig)) - return respfrompost, err + var err = c.post(&resp, url.Path(clientAttrCertPath+"/generate-and-download"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.JSON(keyStoreConfig)) + return resp, err } // UploadCertificatePrivateKey uploads a certificate and eventually a private key. func (c *Client) UploadCertificatePrivateKey(realmName, idClient, attr string, file []byte) (CertificateRepresentation, error) { var resp = CertificateRepresentation{} - var err = c.post(url.Path(clientAttrCertPath+"/upload"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.Reader(bytes.NewReader(file))) - return respfrompost, err + var err = c.post(&resp, url.Path(clientAttrCertPath+"/upload"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.Reader(bytes.NewReader(file))) + return resp, err } // UploadCertificate uploads only a certificate, not the private key. func (c *Client) UploadCertificate(realmName, idClient, attr string, file []byte) (CertificateRepresentation, error) { var resp = CertificateRepresentation{} - var err = c.post(url.Path(clientAttrCertPath+"/upload-certificate"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.Reader(bytes.NewReader(file))) - return respfrompost, err + var err = c.post(&resp, url.Path(clientAttrCertPath+"/upload-certificate"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.Reader(bytes.NewReader(file))) + return resp, err } diff --git a/client_initial_access.go b/client_initial_access.go index 39fa52d..46aeb2d 100644 --- a/client_initial_access.go +++ b/client_initial_access.go @@ -12,8 +12,8 @@ const ( // CreateClientInitialAccess creates a new initial access token. func (c *Client) CreateClientInitialAccess(realmName string, access ClientInitialAccessCreatePresentation) (ClientInitialAccessPresentation, error) { var resp = ClientInitialAccessPresentation{} - var err = c.post(url.Path(clientInitialAccessPath), url.Param("realm", realmName), body.JSON(access)) - return respasf, err + var err = c.post(&resp, url.Path(clientInitialAccessPath), url.Param("realm", realmName), body.JSON(access)) + return resp, err } // GetClientInitialAccess returns a list of clients initial access. diff --git a/client_role_mappings.go b/client_role_mappings.go index 0aeb79e..0692f73 100644 --- a/client_role_mappings.go +++ b/client_role_mappings.go @@ -11,7 +11,7 @@ const ( // CreateClientsRoleMapping add client-level roles to the user role mapping. func (c *Client) CreateClientsRoleMapping(realmName, groupID, clientID string, roles []RoleRepresentation) error { - return c.post(url.Path(clientRoleMappingPath), url.Param("realm", realmName), url.Param("id", groupID), url.Param("client", clientID), body.JSON(roles)) + return c.post(nil, url.Path(clientRoleMappingPath), url.Param("realm", realmName), url.Param("id", groupID), url.Param("client", clientID), body.JSON(roles)) } // GetClientsRoleMapping gets client-level role mappings for the user, and the app. diff --git a/clients_test.go b/clients_test.go index a4e746c..0daf50c 100644 --- a/clients_test.go +++ b/clients_test.go @@ -1,28 +1,22 @@ package keycloak import ( + "encoding/json" "fmt" "testing" - "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -func initTest(t *testing.T) *Client { - var config = Config{ - Addr: "http://127.0.0.1", - Username: "admin", - Password: "admin", - Timeout: time.Second * 20, - } - var client *Client - { - var err error - client, err = New(config) - require.Nil(t, err, "could not create client") +func TestPlayground(t *testing.T) { + var client = initTest(t) + var clients, err = client.GetClients("master") + + for _, c := range clients { + printStruct(c) } - return client + + assert.Nil(t, err) } func TestCreateRealm(t *testing.T) { @@ -47,3 +41,13 @@ func TestGetSecret(t *testing.T) { fmt.Println(*(c.Value)) assert.Nil(t, err) } + +func printStruct(data interface{}) { + var s, err = json.Marshal(data) + if err != nil { + fmt.Println("could not marshal json") + return + } + fmt.Println(string(s)) + fmt.Println() +} diff --git a/keycloak_client.go b/keycloak_client.go index 644880f..b9b16cf 100644 --- a/keycloak_client.go +++ b/keycloak_client.go @@ -166,7 +166,7 @@ func (c *Client) get(data interface{}, plugins ...plugin.Plugin) error { } } -func (c *Client) post(plugins ...plugin.Plugin) error { +func (c *Client) post(data interface{}, plugins ...plugin.Plugin) error { var req = c.httpClient.Post() req = applyPlugins(req, c.accessToken, plugins...) var resp *gentleman.Response @@ -186,11 +186,19 @@ func (c *Client) post(plugins ...plugin.Plugin) error { return errors.Wrap(err, "could not get token") } } - return c.post(plugins...) + return c.post(data, plugins...) case resp.StatusCode >= 400: return fmt.Errorf("invalid status code: '%v': %v", resp.RawResponse.Status, string(resp.Bytes())) case resp.StatusCode >= 200: - return nil + switch resp.Header.Get("Content-Type") { + case "application/json": + return resp.JSON(data) + case "application/octet-stream": + data = resp.Bytes() + return nil + default: + return fmt.Errorf("unkown http content-type: %v", resp.Header.Get("Content-Type")) + } default: return fmt.Errorf("unknown response status code: %v", resp.StatusCode) } diff --git a/keycloak_client_test.go b/keycloak_client_test.go new file mode 100644 index 0000000..1220810 --- /dev/null +++ b/keycloak_client_test.go @@ -0,0 +1,40 @@ +package keycloak + +import ( + "flag" + "fmt" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +var ( + hostPort = flag.String("hostport", "10.244.18.2:80", "keycloak host:port") + username = flag.String("username", "admin", "keycloak user name") + password = flag.String("password", "admin", "keycloak password") + to = flag.Int("timeout", 20, "timeout in seconds") +) + +func TestMain(m *testing.M) { + flag.Parse() + result := m.Run() + os.Exit(result) +} + +func initTest(t *testing.T) *Client { + var config = Config{ + Addr: fmt.Sprintf("http://%s", *hostPort), + Username: *username, + Password: *password, + Timeout: time.Duration(*to) * time.Second, + } + var client *Client + { + var err error + client, err = New(config) + require.Nil(t, err, "could not create client") + } + return client +} diff --git a/realm.go b/realm.go index a67436a..a1207c4 100644 --- a/realm.go +++ b/realm.go @@ -20,7 +20,7 @@ func (c *Client) GetRealms() ([]RealmRepresentation, error) { // CreateRealm creates the realm from its RealmRepresentation. func (c *Client) CreateRealm(realm RealmRepresentation) error { - return c.post(url.Path(realmRootPath), body.JSON(realm)) + return c.post(nil, url.Path(realmRootPath), body.JSON(realm)) } // GetRealm get the top level represention of the realm. Nested information like users are diff --git a/users.go b/users.go index 36143f6..676b3de 100644 --- a/users.go +++ b/users.go @@ -30,7 +30,7 @@ func (c *Client) GetUsers(realmName string, paramKV ...string) ([]UserRepresenta // CreateUser creates the user from its UserRepresentation. The username must be unique. func (c *Client) CreateUser(realmName string, user UserRepresentation) error { - return c.post(url.Path(userPath), url.Param("realm", realmName), body.JSON(user)) + return c.post(nil, url.Path(userPath), url.Param("realm", realmName), body.JSON(user)) } // CountUsers returns the number of users in the realm. diff --git a/users_test.go b/users_test.go new file mode 100644 index 0000000..d3452b5 --- /dev/null +++ b/users_test.go @@ -0,0 +1,23 @@ +package keycloak + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetUsers(t *testing.T) { + var c = initTest(t) + var users []UserRepresentation + { + var err error + users, err = c.GetUsers("master") + require.Nil(t, err, "could not get users") + } + for _, i := range users { + fmt.Println(i.Credentials) + assert.NotZero(t, *i.Username) + } +}