From 112a4a52113ba9696abf0239022b912656eb000f Mon Sep 17 00:00:00 2001 From: Sonia <31467983+bsoniam@users.noreply.github.com> Date: Wed, 11 Sep 2019 16:35:44 +0200 Subject: [PATCH] [CLOUDTRUST-1261] Error handling --- Gopkg.lock | 14 ++++---- clients.go | 4 +-- errormessages.go | 21 +++++++++++ integration/integration.go | 13 ------- keycloak_client.go | 72 +++++++++++++++++++++----------------- oidc_verifier.go | 2 +- users.go | 10 +++--- 7 files changed, 76 insertions(+), 60 deletions(-) create mode 100644 errormessages.go diff --git a/Gopkg.lock b/Gopkg.lock index a7ba409..5ac9739 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -115,7 +115,7 @@ [[projects]] branch = "master" - digest = "1:a530f8e0c0ee8a3b440f9f0b0e9f4e5d5e47cfe3a581086ce32cd8ba114ddf4f" + digest = "1:086760278d762dbb0e9a26e09b57f04c89178c86467d8d94fae47d64c222f328" name = "golang.org/x/crypto" packages = [ "ed25519", @@ -123,11 +123,11 @@ "pbkdf2", ] pruneopts = "" - revision = "9756ffdc24725223350eb3266ffb92590d28f278" + revision = "4def268fd1a49955bfb3dda92fe3db4f924f2285" [[projects]] branch = "master" - digest = "1:87c06c289123bf8be0a776c57ca40ce075f6c598a905ff2ff8ba40fba0d5d17c" + digest = "1:31cd6e3c114e17c5f0c9e8b0bcaa3025ab3c221ce36323c7ce1acaa753d0d0aa" name = "golang.org/x/net" packages = [ "context", @@ -136,7 +136,7 @@ "publicsuffix", ] pruneopts = "" - revision = "ba9fcec4b297b415637633c5a6e8fa592e4a16c3" + revision = "da137c7871d730100384dbcf36e6f8fa493aef5b" [[projects]] branch = "master" @@ -175,7 +175,7 @@ version = "v0.3.2" [[projects]] - digest = "1:0568e577f790e9bd0420521cff50580f9b38165a38f217ce68f55c4bbaa97066" + digest = "1:47f391ee443f578f01168347818cb234ed819521e49e4d2c8dd2fb80d48ee41a" name = "google.golang.org/appengine" packages = [ "internal", @@ -187,8 +187,8 @@ "urlfetch", ] pruneopts = "" - revision = "5f2a59506353b8d5ba8cbbcd9f3c1f41f1eaf079" - version = "v1.6.2" + revision = "b2f4a3cf3c67576a2ee09e1fe62656a5086ce880" + version = "v1.6.1" [[projects]] digest = "1:e3250d192192f02fbb143d50de437cbe967d6be7bd9fad671600942a33269d08" diff --git a/clients.go b/clients.go index 28c033f..ebc92a1 100644 --- a/clients.go +++ b/clients.go @@ -1,7 +1,7 @@ package keycloak import ( - "fmt" + "errors" "gopkg.in/h2non/gentleman.v2/plugins/url" ) @@ -17,7 +17,7 @@ const ( // viewableOnly (filter clients that cannot be viewed in full by admin, default="false") func (c *Client) GetClients(accessToken string, realmName string, paramKV ...string) ([]ClientRepresentation, error) { if len(paramKV)%2 != 0 { - return nil, fmt.Errorf("the number of key/val parameters should be even") + return nil, errors.New(MsgErrInvalidParam + "." + EvenParams) } var resp = []ClientRepresentation{} diff --git a/errormessages.go b/errormessages.go new file mode 100644 index 0000000..27a71fd --- /dev/null +++ b/errormessages.go @@ -0,0 +1,21 @@ +package keycloak + +const ( + MsgErrMissingParam = "missingParameter" + MsgErrInvalidParam = "invalidParameter" + MsgErrCannotObtain = "cannotObtain" + MsgErrCannotMarshal = "cannotMarshal" + MsgErrCannotUnmarshal = "cannotUnmarshal" + MsgErrCannotParse = "cannotParse" + MsgErrCannotCreate = "cannotCreate" + MsgErrUnkownHTTPContentType = "unkownHTTPContentType" + MsgErrUnknownResponseStatusCode = "unknownResponseStatusCode" + + EvenParams = "key/valParametersShouldBeEven" + TokenProviderURL = "tokenProviderURL" + APIURL = "APIURL" + TokenMsg = "token" + Response = "response" + AccessToken = "accessToken" + OIDCProvider = "OIDCProvider" +) diff --git a/integration/integration.go b/integration/integration.go index c2c5cc0..b6794da 100644 --- a/integration/integration.go +++ b/integration/integration.go @@ -16,19 +16,6 @@ const ( user = "version" ) -// This should be converted into -// GetClient(accessToken string, realmName, idClient string) (kc.ClientRepresentation, error) -// GetClientRoleMappings(accessToken string, realmName, userID, clientID string) ([]kc.RoleRepresentation, error) -// AddClientRolesToUserRoleMapping(accessToken string, realmName, userID, clientID string, roles []kc.RoleRepresentation) error -// GetRealmLevelRoleMappings(accessToken string, realmName, userID string) ([]kc.RoleRepresentation, error) -// ResetPassword(accessToken string, realmName string, userID string) error -// SendVerifyEmail(accessToken string, realmName string, userID string) error - -// GetRoles(accessToken string, realmName string) ([]kc.RoleRepresentation, error) -// GetRole(accessToken string, realmName string, roleID string) (kc.RoleRepresentation, error) -// GetClientRoles(accessToken string, realmName, idClient string) ([]kc.RoleRepresentation, error) -// CreateClientRole(accessToken string, realmName, clientID string, role kc.RoleRepresentation) (string, error) - func main() { var conf = getKeycloakConfig() var client, err = keycloak.New(*conf) diff --git a/keycloak_client.go b/keycloak_client.go index 51ea9cc..35c0ad1 100644 --- a/keycloak_client.go +++ b/keycloak_client.go @@ -2,11 +2,13 @@ package keycloak import ( "encoding/json" + "fmt" "net/http" "net/url" "time" + commonhttp "github.com/cloudtrust/common-service/errors" "github.com/pkg/errors" "gopkg.in/h2non/gentleman.v2" "gopkg.in/h2non/gentleman.v2/plugin" @@ -49,7 +51,7 @@ func New(config Config) (*Client, error) { var err error uToken, err = url.Parse(config.AddrTokenProvider) if err != nil { - return nil, errors.Wrap(err, "could not parse Token Provider URL") + return nil, errors.Wrap(err, MsgErrCannotParse+"."+TokenProviderURL) } } @@ -58,7 +60,7 @@ func New(config Config) (*Client, error) { var err error uAPI, err = url.Parse(config.AddrAPI) if err != nil { - return nil, errors.Wrap(err, "could not parse API URL") + return nil, errors.Wrap(err, MsgErrCannotParse+"."+APIURL) } } @@ -87,7 +89,7 @@ func New(config Config) (*Client, error) { return client, nil } -// GetToken returns a valid token from keycloak. +// GetToken returns a valid token from keycloak func (c *Client) GetToken(realm string, username string, password string) (string, error) { var req *gentleman.Request { @@ -104,7 +106,7 @@ func (c *Client) GetToken(realm string, username string, password string) (strin var err error resp, err = req.Do() if err != nil { - return "", errors.Wrap(err, "could not get token") + return "", errors.Wrap(err, MsgErrCannotObtain+"."+TokenMsg) } } defer resp.Close() @@ -114,7 +116,7 @@ func (c *Client) GetToken(realm string, username string, password string) (strin var err error err = resp.JSON(&unmarshalledBody) if err != nil { - return "", errors.Wrap(err, "could not unmarshal response") + return "", errors.Wrap(err, MsgErrCannotUnmarshal+"."+Response) } } @@ -123,7 +125,7 @@ func (c *Client) GetToken(realm string, username string, password string) (strin var ok bool accessToken, ok = unmarshalledBody["access_token"] if !ok { - return "", fmt.Errorf("could not find access token in response body") + return "", fmt.Errorf(MsgErrMissingParam + "." + AccessToken) } } @@ -158,7 +160,7 @@ func (c *Client) get(accessToken string, data interface{}, plugins ...plugin.Plu var err error resp, err = req.Do() if err != nil { - return errors.Wrap(err, "could not get response") + return errors.Wrap(err, MsgErrCannotObtain+"."+Response) } switch { @@ -171,10 +173,7 @@ func (c *Client) get(accessToken string, data interface{}, plugins ...plugin.Plu var response map[string]string err := json.Unmarshal(resp.Bytes(), &response) if message, ok := response["errorMessage"]; ok && err == nil { - return HTTPError{ - HTTPStatus: resp.StatusCode, - Message: message, - } + return whitelistErrors(resp.StatusCode, message) } return HTTPError{ HTTPStatus: resp.StatusCode, @@ -188,10 +187,10 @@ func (c *Client) get(accessToken string, data interface{}, plugins ...plugin.Plu data = resp.Bytes() return nil default: - return fmt.Errorf("unkown http content-type: %v", resp.Header.Get("Content-Type")) + return fmt.Errorf("%s.%v", MsgErrUnkownHTTPContentType, resp.Header.Get("Content-Type")) } default: - return fmt.Errorf("unknown response status code: %v", resp.StatusCode) + return fmt.Errorf("%s.%v", MsgErrUnknownResponseStatusCode, resp.StatusCode) } } } @@ -211,7 +210,7 @@ func (c *Client) post(accessToken string, data interface{}, plugins ...plugin.Pl var err error resp, err = req.Do() if err != nil { - return "", errors.Wrap(err, "could not get response") + return "", errors.Wrap(err, MsgErrCannotObtain+"."+Response) } switch { @@ -224,10 +223,7 @@ func (c *Client) post(accessToken string, data interface{}, plugins ...plugin.Pl var response map[string]string err := json.Unmarshal(resp.Bytes(), &response) if message, ok := response["errorMessage"]; ok && err == nil { - return "", HTTPError{ - HTTPStatus: resp.StatusCode, - Message: message, - } + return "", whitelistErrors(resp.StatusCode, message) } return "", HTTPError{ HTTPStatus: resp.StatusCode, @@ -246,7 +242,7 @@ func (c *Client) post(accessToken string, data interface{}, plugins ...plugin.Pl return location, nil } default: - return "", fmt.Errorf("unknown response status code: %v", resp.StatusCode) + return "", fmt.Errorf("%s.%v", MsgErrUnknownResponseStatusCode, resp.StatusCode) } } } @@ -266,7 +262,7 @@ func (c *Client) delete(accessToken string, plugins ...plugin.Plugin) error { var err error resp, err = req.Do() if err != nil { - return errors.Wrap(err, "could not get response") + return errors.Wrap(err, MsgErrCannotObtain+"."+Response) } switch { @@ -279,10 +275,7 @@ func (c *Client) delete(accessToken string, plugins ...plugin.Plugin) error { var response map[string]string err := json.Unmarshal(resp.Bytes(), &response) if message, ok := response["errorMessage"]; ok && err == nil { - return HTTPError{ - HTTPStatus: resp.StatusCode, - Message: message, - } + return whitelistErrors(resp.StatusCode, message) } return HTTPError{ HTTPStatus: resp.StatusCode, @@ -314,7 +307,7 @@ func (c *Client) put(accessToken string, plugins ...plugin.Plugin) error { var err error resp, err = req.Do() if err != nil { - return errors.Wrap(err, "could not get response") + return errors.Wrap(err, MsgErrCannotObtain+"."+Response) } switch { @@ -327,10 +320,7 @@ func (c *Client) put(accessToken string, plugins ...plugin.Plugin) error { var response map[string]string err := json.Unmarshal(resp.Bytes(), &response) if message, ok := response["errorMessage"]; ok && err == nil { - return HTTPError{ - HTTPStatus: resp.StatusCode, - Message: message, - } + return whitelistErrors(resp.StatusCode, message) } return HTTPError{ HTTPStatus: resp.StatusCode, @@ -383,7 +373,7 @@ func extractHostFromToken(token string) (string, error) { var err error u, err = url.Parse(issuer) if err != nil { - return "", errors.Wrap(err, "could not parse Token issuer URL") + return "", errors.Wrap(err, MsgErrCannotParse+"."+TokenProviderURL) } } @@ -394,13 +384,13 @@ func extractIssuerFromToken(token string) (string, error) { payload, _, err := jwt.Parse(token) if err != nil { - return "", errors.Wrap(err, "could not parse Token") + return "", errors.Wrap(err, MsgErrCannotParse+"."+TokenMsg) } var jot Token if err = jwt.Unmarshal(payload, &jot); err != nil { - return "", errors.Wrap(err, "could not unmarshall token") + return "", errors.Wrap(err, MsgErrCannotUnmarshal+"."+TokenMsg) } return jot.Issuer, nil @@ -421,6 +411,24 @@ func str(s string) *string { return &s } +func whitelistErrors(statusCode int, message string) error { + // whitelist errors from Keycloak + + switch message { + //POST account/credentials/password with error message "invalidPasswordExistingMessage" + case "invalidPasswordExistingMessage": + return commonhttp.Error{ + Status: statusCode, + Message: "keycloak." + message, + } + default: + return HTTPError{ + HTTPStatus: statusCode, + Message: message, + } + } +} + // Token is JWT token. // We need to define our own structure as the library define aud as a string but it can also be a string array. // To fix this issue, we remove aud as we do not use it here. diff --git a/oidc_verifier.go b/oidc_verifier.go index 1b59853..71507c1 100644 --- a/oidc_verifier.go +++ b/oidc_verifier.go @@ -55,7 +55,7 @@ func (vc *verifierCache) GetOidcVerifier(realm string) (OidcVerifier, error) { var issuer = fmt.Sprintf("%s/auth/realms/%s", vc.tokenProviderURL.String(), realm) oidcProvider, err = oidc.NewProvider(context.Background(), issuer) if err != nil { - return nil, errors.Wrap(err, "could not create oidc provider") + return nil, errors.Wrap(err, MsgErrCannotCreate+"."+OIDCProvider) } } diff --git a/users.go b/users.go index 575e914..fc403cd 100644 --- a/users.go +++ b/users.go @@ -1,7 +1,7 @@ package keycloak import ( - "fmt" + "errors" "gopkg.in/h2non/gentleman.v2/plugins/body" "gopkg.in/h2non/gentleman.v2/plugins/headers" @@ -33,7 +33,7 @@ const ( func (c *Client) GetUsers(accessToken string, reqRealmName, targetRealmName string, paramKV ...string) (UsersPageRepresentation, error) { var resp UsersPageRepresentation if len(paramKV)%2 != 0 { - return resp, fmt.Errorf("the number of key/val parameters should be even") + return resp, errors.New(MsgErrInvalidParam + "." + EvenParams) } var plugins = append(createQueryPlugins(paramKV...), url.Path(usersAdminExtensionApiPath), url.Param("realmReq", reqRealmName), url.Param("realm", targetRealmName)) @@ -85,7 +85,7 @@ func (c *Client) ResetPassword(accessToken string, realmName, userID string, cre // 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 { - return fmt.Errorf("the number of key/val parameters should be even") + return errors.New(MsgErrInvalidParam + "." + EvenParams) } var plugins = append(createQueryPlugins(paramKV...), url.Path(sendVerifyEmailPath), url.Param("realm", realmName), url.Param("id", userID)) @@ -96,7 +96,7 @@ func (c *Client) SendVerifyEmail(accessToken string, realmName string, userID st // ExecuteActionsEmail sends an update account email to the user. An email contains a link the user can click to perform a set of required actions. func (c *Client) ExecuteActionsEmail(accessToken string, realmName string, userID string, actions []string, paramKV ...string) error { if len(paramKV)%2 != 0 { - return fmt.Errorf("the number of key/val parameters should be even") + return errors.New(MsgErrInvalidParam + "." + EvenParams) } var plugins = append(createQueryPlugins(paramKV...), url.Path(executeActionsEmailPath), url.Param("realm", realmName), url.Param("id", userID), body.JSON(actions)) @@ -119,7 +119,7 @@ func (c *Client) SendNewEnrolmentCode(accessToken string, realmName string, user // SendReminderEmail sends a reminder email to a user func (c *Client) SendReminderEmail(accessToken string, realmName string, userID string, paramKV ...string) error { if len(paramKV)%2 != 0 { - return fmt.Errorf("the number of key/val parameters should be even") + return errors.New(MsgErrInvalidParam + "." + EvenParams) } var newParamKV = append(paramKV, "userid", userID)