From c5e13ab423756cd29f249b93d7c068ad18ebc508 Mon Sep 17 00:00:00 2001 From: harture <31417989+harture@users.noreply.github.com> Date: Fri, 27 Sep 2019 07:38:19 +0200 Subject: [PATCH] [CLOUDTRUST-1502] Multi-token API --- account.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++ credentials.go | 64 +++++++++++++++++++++++++++++++++++++ definitions.go | 21 ++++-------- keycloak_client.go | 25 +++++++++++---- realm.go | 15 +++++++-- users.go | 72 +++++++---------------------------------- 6 files changed, 194 insertions(+), 83 deletions(-) create mode 100644 account.go create mode 100644 credentials.go diff --git a/account.go b/account.go new file mode 100644 index 0000000..105add9 --- /dev/null +++ b/account.go @@ -0,0 +1,80 @@ +package keycloak + +import ( + "gopkg.in/h2non/gentleman.v2/plugins/body" + "gopkg.in/h2non/gentleman.v2/plugins/headers" + "gopkg.in/h2non/gentleman.v2/plugins/url" +) + +const ( + accountPath = "/auth/realms/:realm/account" + accountExtensionAPIPath = "/auth/realms/master/api/account/realms/:realm" + accountPasswordPath = accountExtensionAPIPath + "/credentials/password" + accountCredentialsPath = accountPath + "/credentials" + accountCredentialsRegistratorsPath = accountCredentialsPath + "/registrators" + accountCredentialIDPath = accountCredentialsPath + "/:credentialID" + accountCredentialLabelPath = accountCredentialIDPath + "/label" + accountMoveFirstPath = accountCredentialIDPath + "/moveToFirst" + accountMoveAfterPath = accountCredentialIDPath + "/moveAfter/:previousCredentialID" +) + +// GetCredentials returns the list of credentials of the user +func (c *AccountClient) GetCredentials(accessToken string, realmName string) ([]CredentialRepresentation, error) { + var resp = []CredentialRepresentation{} + var err = c.client.get(accessToken, &resp, url.Path(accountCredentialsPath), url.Param("realm", realmName), headers.Set("Accept", "application/json")) + return resp, err +} + +// GetCredentialRegistrators returns list of credentials types available for the user +func (c *AccountClient) GetCredentialRegistrators(accessToken string, realmName string) ([]string, error) { + var resp = []string{} + var err = c.client.get(accessToken, &resp, url.Path(accountCredentialsRegistratorsPath), url.Param("realm", realmName), headers.Set("Accept", "application/json")) + return resp, err +} + +// UpdateLabelCredential updates the label of credential +func (c *AccountClient) UpdateLabelCredential(accessToken string, realmName string, credentialID string, label string) error { + return c.client.put(accessToken, url.Path(accountCredentialLabelPath), url.Param("realm", realmName), url.Param("credentialID", credentialID), body.String(label), headers.Set("Accept", "application/json"), headers.Set("Content-Type", "text/plain")) +} + +// DeleteCredential deletes the credential +func (c *AccountClient) DeleteCredential(accessToken string, realmName string, credentialID string) error { + return c.client.delete(accessToken, url.Path(accountCredentialIDPath), url.Param("realm", realmName), url.Param("credentialID", credentialID), headers.Set("Accept", "application/json")) +} + +// MoveToFirst moves the credential at the top of the list +func (c *AccountClient) MoveToFirst(accessToken string, realmName string, credentialID string) error { + _, err := c.client.post(accessToken, nil, url.Path(accountMoveFirstPath), url.Param("realm", realmName), url.Param("credentialID", credentialID), headers.Set("Accept", "application/json")) + return err +} + +// MoveAfter moves the credential after the specified one into the list +func (c *AccountClient) MoveAfter(accessToken string, realmName string, credentialID string, previousCredentialID string) error { + _, err := c.client.post(accessToken, nil, url.Path(accountMoveAfterPath), url.Param("realm", realmName), url.Param("credentialID", credentialID), url.Param("previousCredentialID", previousCredentialID), headers.Set("Accept", "application/json")) + return err +} + +// UpdatePassword updates the user's password +// Parameters: realm, currentPassword, newPassword, confirmPassword +func (c *AccountClient) UpdatePassword(accessToken, realm, currentPassword, newPassword, confirmPassword string) (string, error) { + var m = map[string]string{"currentPassword": currentPassword, "newPassword": newPassword, "confirmation": confirmPassword} + return c.client.post(accessToken, nil, url.Path(accountPasswordPath), url.Param("realm", realm), body.JSON(m)) +} + +// GetAccount provides the user's information +func (c *AccountClient) GetAccount(accessToken string, realm string) (UserRepresentation, error) { + var resp = UserRepresentation{} + var err = c.client.get(accessToken, &resp, url.Path(accountExtensionAPIPath), url.Param("realm", realm), headers.Set("Accept", "application/json")) + return resp, err +} + +// UpdateAccount updates the user's information +func (c *AccountClient) UpdateAccount(accessToken string, realm string, user UserRepresentation) error { + _, err := c.client.post(accessToken, nil, url.Path(accountExtensionAPIPath), url.Param("realm", realm), body.JSON(user)) + return err +} + +// DeleteAccount delete current user +func (c *AccountClient) DeleteAccount(accessToken string, realmName string) error { + return c.client.delete(accessToken, url.Path(accountExtensionAPIPath), url.Param("realm", realmName), headers.Set("Accept", "application/json")) +} diff --git a/credentials.go b/credentials.go new file mode 100644 index 0000000..037db92 --- /dev/null +++ b/credentials.go @@ -0,0 +1,64 @@ +package keycloak + +import ( + "gopkg.in/h2non/gentleman.v2/plugins/body" + "gopkg.in/h2non/gentleman.v2/plugins/url" +) + +const ( + resetPasswordPath = userIDPath + "/reset-password" + credentialsPath = userIDPath + "/credentials" + credentialsTypesPath = realmPath + "/credentialTypes" + credentialIDPath = "/:credentialID" + moveFirstPath = credentialIDPath + "/moveToFirst" + moveAfterPath = credentialIDPath + "/moveAfter/:previousCredentialID" +) + +// ResetPassword resets password of the user. +func (c *Client) ResetPassword(accessToken string, realmName, userID string, cred CredentialRepresentation) error { + return c.put(accessToken, url.Path(resetPasswordPath), url.Param("realm", realmName), url.Param("id", userID), body.JSON(cred)) +} + +// GetCredentials returns the list of credentials of the user +func (c *Client) GetCredentials(accessToken string, realmName string, userID string) ([]CredentialRepresentation, error) { + var resp = []CredentialRepresentation{} + + var err = c.get(accessToken, &resp, url.Path(credentialsPath), url.Param("realm", realmName), url.Param("id", userID)) + return resp, err +} + +// GetCredentialTypes returns list of credentials types available for the realm +func (c *Client) GetCredentialTypes(accessToken string, realmName string) ([]string, error) { + var resp = []string{} + var err = c.get(accessToken, &resp, url.Path(credentialsTypesPath), url.Param("realm", realmName)) + return resp, err +} + +// UpdateCredential updates the credential +func (c *Client) UpdateCredential(accessToken string, realmName string, userID string, credentialID string, credential CredentialRepresentation) error { + return c.put(accessToken, url.Path(credentialIDPath), url.Param("realm", realmName), url.Param("id", userID), url.Param("credentialID", credentialID), body.JSON(credential)) +} + +// DeleteCredential deletes the credential +func (c *Client) DeleteCredential(accessToken string, realmName string, userID string, credentialID string) error { + return c.delete(accessToken, url.Path(credentialIDPath), url.Param("realm", realmName), url.Param("id", userID), url.Param("credentialID", credentialID)) +} + +// MoveToFirst moves the credential at the top of the list +func (c *Client) MoveToFirst(accessToken string, realmName string, userID string, credentialID string) error { + _, err := c.post(accessToken, url.Path(moveFirstPath), url.Param("realm", realmName), url.Param("id", userID), url.Param("credentialID", credentialID)) + return err +} + +// MoveAfter moves the credential after the specified one into the list +func (c *Client) MoveAfter(accessToken string, realmName string, userID string, credentialID string, previousCredentialID string) error { + _, err := c.post(accessToken, url.Path(moveAfterPath), url.Param("realm", realmName), url.Param("id", userID), url.Param("credentialID", credentialID), url.Param("previousCredentialID", previousCredentialID)) + return err +} + +// UpdatePassword updates the user's password +// Parameters: realm, currentPassword, newPassword, confirmPassword +func (c *Client) UpdatePassword(accessToken, realm, currentPassword, newPassword, confirmPassword string) (string, error) { + var m = map[string]string{"currentPassword": currentPassword, "newPassword": newPassword, "confirmation": confirmPassword} + return c.post(accessToken, nil, url.Path(accountPasswordPath), url.Param("realm", realm), body.JSON(m)) +} diff --git a/definitions.go b/definitions.go index 4d5c32a..c2c482d 100644 --- a/definitions.go +++ b/definitions.go @@ -201,20 +201,13 @@ type ConfigPropertyRepresentation struct { } type CredentialRepresentation struct { - Id *string `json:"id,omitempty"` - Algorithm *string `json:"algorithm,omitempty"` - Config *map[string][]string `json:"config,omitempty"` - Counter *int32 `json:"counter,omitempty"` - CreatedDate *int64 `json:"createdDate,omitempty"` - Device *string `json:"device,omitempty"` - Digits *int32 `json:"digits,omitempty"` - HashIterations *int32 `json:"hashIterations,omitempty"` - HashedSaltedValue *string `json:"hashedSaltedValue,omitempty"` - Period *int32 `json:"period,omitempty"` - Salt *string `json:"salt,omitempty"` - Temporary *bool `json:"temporary,omitempty"` - Type *string `json:"type,omitempty"` - Value *string `json:"value,omitempty"` + Id *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + UserLabel *string `json:"userLabel,omitempty"` + CreatedDate *int64 `json:"createdDate,omitempty"` + CredentialData *string `json:"credentialData,omitempty"` + Value *string `json:"value,omitempty"` + Temporary *bool `json:"temporary,omitempty"` } type EventRepresentation struct { diff --git a/keycloak_client.go b/keycloak_client.go index 8e4ec0b..b1fd4d2 100644 --- a/keycloak_client.go +++ b/keycloak_client.go @@ -32,9 +32,14 @@ type Config struct { type Client struct { apiURL *url.URL httpClient *gentleman.Client + account *AccountClient verifierProvider OidcVerifierProvider } +type AccountClient struct { + client *Client +} + // HTTPError is returned when an error occured while contacting the keycloak instance. type HTTPError struct { HTTPStatus int @@ -65,12 +70,6 @@ func New(config Config) (*Client, error) { } } - var httpClient = gentleman.New() - { - httpClient = httpClient.URL(uAPI.String()) - httpClient = httpClient.Use(timeout.Request(config.Timeout)) - } - // Use default values when clients are not initializing these values cacheTTL := config.CacheTTL if cacheTTL == 0 { @@ -81,12 +80,22 @@ func New(config Config) (*Client, error) { errTolerance = time.Minute } + var httpClient = gentleman.New() + { + httpClient = httpClient.URL(uAPI.String()) + httpClient = httpClient.Use(timeout.Request(config.Timeout)) + } + var client = &Client{ apiURL: uAPI, httpClient: httpClient, verifierProvider: NewVerifierCache(uToken, cacheTTL, errTolerance), } + client.account = &AccountClient{ + client: client, + } + return client, nil } @@ -145,6 +154,10 @@ func (c *Client) VerifyToken(realmName string, accessToken string) error { return verifier.Verify(accessToken) } +func (c *Client) AccountClient() *AccountClient { + return c.account +} + // get is a HTTP get method. func (c *Client) get(accessToken string, data interface{}, plugins ...plugin.Plugin) error { var err error diff --git a/realm.go b/realm.go index b850bbb..27eae0c 100644 --- a/realm.go +++ b/realm.go @@ -2,13 +2,15 @@ package keycloak import ( "gopkg.in/h2non/gentleman.v2/plugins/body" + "gopkg.in/h2non/gentleman.v2/plugins/headers" "gopkg.in/h2non/gentleman.v2/plugins/url" ) const ( - realmRootPath = "/auth/admin/realms" - realmPath = realmRootPath + "/:realm" - exportRealmPath = "/auth/realms/:realm/export/realm" + realmRootPath = "/auth/admin/realms" + realmPath = realmRootPath + "/:realm" + realmCredentialRegistrators = realmPath + "/credential-registrators" + exportRealmPath = "/auth/realms/:realm/export/realm" ) // GetRealms get the top level represention of all the realms. Nested information like users are @@ -49,3 +51,10 @@ func (c *Client) ExportRealm(accessToken string, realmName string) (RealmReprese var err = c.get(accessToken, &resp, url.Path(exportRealmPath), url.Param("realm", realmName)) return resp, err } + +// GetRealmCredentialRegistrators returns list of credentials types available for the realm +func (c *Client) GetRealmCredentialRegistrators(accessToken string, realmName string) ([]string, error) { + var resp = []string{} + var err = c.get(accessToken, &resp, url.Path(realmCredentialRegistrators), url.Param("realm", realmName), headers.Set("Accept", "application/json")) + return resp, err +} diff --git a/users.go b/users.go index fc403cd..26ad081 100644 --- a/users.go +++ b/users.go @@ -4,26 +4,20 @@ import ( "errors" "gopkg.in/h2non/gentleman.v2/plugins/body" - "gopkg.in/h2non/gentleman.v2/plugins/headers" "gopkg.in/h2non/gentleman.v2/plugins/url" ) const ( - userPath = "/auth/admin/realms/:realm/users" - usersAdminExtensionApiPath = "/auth/realms/:realmReq/api/admin/realms/:realm/users" - userCountPath = userPath + "/count" - userIDPath = userPath + "/:id" - userGroupsPath = userIDPath + "/groups" - resetPasswordPath = userIDPath + "/reset-password" - sendVerifyEmailPath = userIDPath + "/send-verify-email" - executeActionsEmailPath = userIDPath + "/execute-actions-email" - sendReminderEmailPath = "/auth/realms/:realm/onboarding/sendReminderEmail" - smsAPI = "/auth/realms/:realm/smsApi" - sendNewEnrolmentCode = smsAPI + "/sendNewCode" - getCredentialsForUserPath = usersAdminExtensionApiPath + "/:id/credentials" - deleteCredentialsForUserPath = getCredentialsForUserPath + "/:credid" - accountPasswordPath = "/auth/realms/master/api/account/realms/:realm/credentials/password" - accountPath = "/auth/realms/master/api/account/realms/:realm/" + userPath = "/auth/admin/realms/:realm/users" + usersAdminExtensionAPIPath = "/auth/realms/:realmReq/api/admin/realms/:realm/users" + userCountPath = userPath + "/count" + userIDPath = userPath + "/:id" + userGroupsPath = userIDPath + "/groups" + sendVerifyEmailPath = userIDPath + "/send-verify-email" + executeActionsEmailPath = userIDPath + "/execute-actions-email" + sendReminderEmailPath = "/auth/realms/:realm/onboarding/sendReminderEmail" + smsAPI = "/auth/realms/:realm/smsApi" + sendNewEnrolmentCode = smsAPI + "/sendNewCode" ) // GetUsers returns a list of users, filtered according to the query parameters. @@ -36,14 +30,14 @@ func (c *Client) GetUsers(accessToken string, reqRealmName, targetRealmName stri return resp, errors.New(MsgErrInvalidParam + "." + EvenParams) } - var plugins = append(createQueryPlugins(paramKV...), url.Path(usersAdminExtensionApiPath), url.Param("realmReq", reqRealmName), url.Param("realm", targetRealmName)) + var plugins = append(createQueryPlugins(paramKV...), url.Path(usersAdminExtensionAPIPath), url.Param("realmReq", reqRealmName), url.Param("realm", targetRealmName)) var err = c.get(accessToken, &resp, plugins...) return resp, err } // CreateUser creates the user from its UserRepresentation. The username must be unique. func (c *Client) CreateUser(accessToken string, reqRealmName, targetRealmName string, user UserRepresentation) (string, error) { - return c.post(accessToken, nil, url.Path(usersAdminExtensionApiPath), url.Param("realmReq", reqRealmName), url.Param("realm", targetRealmName), body.JSON(user)) + return c.post(accessToken, nil, url.Path(usersAdminExtensionAPIPath), url.Param("realmReq", reqRealmName), url.Param("realm", targetRealmName), body.JSON(user)) } // CountUsers returns the number of users in the realm. @@ -77,11 +71,6 @@ func (c *Client) DeleteUser(accessToken string, realmName, userID string) error return c.delete(accessToken, url.Path(userIDPath), url.Param("realm", realmName), url.Param("id", userID)) } -// ResetPassword resets password of the user. -func (c *Client) ResetPassword(accessToken string, realmName, userID string, cred CredentialRepresentation) error { - return c.put(accessToken, url.Path(resetPasswordPath), url.Param("realm", realmName), url.Param("id", userID), body.JSON(cred)) -} - // SendVerifyEmail sends an email-verification email to the user An email contains a link the user can click to verify their email address. func (c *Client) SendVerifyEmail(accessToken string, realmName string, userID string, paramKV ...string) error { if len(paramKV)%2 != 0 { @@ -128,40 +117,3 @@ func (c *Client) SendReminderEmail(accessToken string, realmName string, userID _, err := c.post(accessToken, nil, plugins...) return err } - -// GetCredentialsForUser gets the credential list for a user -func (c *Client) GetCredentialsForUser(accessToken string, realmReq, realmName string, userID string) ([]CredentialRepresentation, error) { - var resp = []CredentialRepresentation{} - var err = c.get(accessToken, &resp, url.Path(getCredentialsForUserPath), url.Param("realmReq", realmReq), url.Param("realm", realmName), url.Param("id", userID)) - return resp, err -} - -// DeleteCredentialsForUser remove credentials for a user -func (c *Client) DeleteCredentialsForUser(accessToken string, realmReq, realmName string, userID string, credentialID string) error { - return c.delete(accessToken, url.Path(deleteCredentialsForUserPath), url.Param("realmReq", realmReq), url.Param("realm", realmName), url.Param("id", userID), url.Param("credid", credentialID)) -} - -// UpdatePassword updates the user's password -// Parameters: realm, currentPassword, newPassword, confirmPassword -func (c *Client) UpdatePassword(accessToken, realm, currentPassword, newPassword, confirmPassword string) (string, error) { - var m = map[string]string{"currentPassword": currentPassword, "newPassword": newPassword, "confirmation": confirmPassword} - return c.post(accessToken, nil, url.Path(accountPasswordPath), url.Param("realm", realm), body.JSON(m)) -} - -// GetAccount provides the user's information -func (c *Client) GetAccount(accessToken string, realm string) (UserRepresentation, error) { - var resp = UserRepresentation{} - var err = c.get(accessToken, &resp, url.Path(accountPath), url.Param("realm", realm), headers.Set("Accept", "application/json")) - return resp, err -} - -// UpdateAccount updates the user's information -func (c *Client) UpdateAccount(accessToken string, realm string, user UserRepresentation) error { - _, err := c.post(accessToken, nil, url.Path(accountPath), url.Param("realm", realm), body.JSON(user)) - return err -} - -// DeleteAccount delete current user -func (c *Client) DeleteAccount(accessToken string, realmName string) error { - return c.delete(accessToken, url.Path(accountPath), url.Param("realm", realmName), headers.Set("Accept", "application/json")) -}