Browse Source

cleanup and prepare to fork

master
Nicolas Massé 5 years ago
parent
commit
577ea35a83
  1. 2
      api/authentication_management.go
  2. 2
      api/client_attribute_certificate.go
  3. 2
      api/client_initial_access.go
  4. 2
      api/client_registration_policy.go
  5. 2
      api/client_role_mappings.go
  6. 2
      api/clients.go
  7. 2
      api/components.go
  8. 2
      api/credentials.go
  9. 2
      api/groups.go
  10. 2
      api/identity_providers.go
  11. 217
      api/keycloak_client.go
  12. 2
      api/realm.go
  13. 2
      api/recovery_code.go
  14. 2
      api/roles.go
  15. 26
      api/statistics.go
  16. 2
      api/users.go
  17. 4
      errormessages.go
  18. 3
      go.mod
  19. 2
      go.sum
  20. 4
      integration/integration-tests.go

2
api/authentication_management.go

@ -1,7 +1,7 @@
package api
import (
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)

2
api/client_attribute_certificate.go

@ -3,7 +3,7 @@ package api
import (
"bytes"
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)

2
api/client_initial_access.go

@ -1,7 +1,7 @@
package api
import (
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)

2
api/client_registration_policy.go

@ -1,7 +1,7 @@
package api
import (
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)

2
api/client_role_mappings.go

@ -1,7 +1,7 @@
package api
import (
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)

2
api/clients.go

@ -3,7 +3,7 @@ package api
import (
"errors"
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)

2
api/components.go

@ -1,7 +1,7 @@
package api
import (
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)

2
api/credentials.go

@ -1,7 +1,7 @@
package api
import (
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/headers"
"gopkg.in/h2non/gentleman.v2/plugins/url"

2
api/groups.go

@ -1,7 +1,7 @@
package api
import (
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)

2
api/identity_providers.go

@ -1,7 +1,7 @@
package api
import (
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)

217
api/keycloak_client.go

@ -1,22 +1,15 @@
package api
import (
"encoding/json"
"regexp"
"fmt"
"net/http"
"net/url"
commonhttp "github.com/cloudtrust/common-service/errors"
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"github.com/pkg/errors"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/plugin"
"gopkg.in/h2non/gentleman.v2/plugins/query"
"gopkg.in/h2non/gentleman.v2/plugins/timeout"
jwt "github.com/gbrlsnchs/jwt/v2"
)
// Client is the keycloak client.
@ -90,9 +83,6 @@ func (c *Client) GetToken(realm string, username string, password string) (strin
}
}
// fmt.Printf("%s", accessToken.(string))
// fmt.Println()
return accessToken.(string), nil
}
@ -101,7 +91,7 @@ func (c *Client) get(accessToken string, data interface{}, plugins ...plugin.Plu
var err error
var req = c.httpClient.Get()
req = applyPlugins(req, plugins...)
req, err = setAuthorisationAndHostHeaders(req, accessToken)
req = setAuthorisationHeader(req, accessToken)
if err != nil {
return err
@ -115,23 +105,21 @@ func (c *Client) get(accessToken string, data interface{}, plugins ...plugin.Plu
return errors.Wrap(err, keycloak.MsgErrCannotObtain+"."+keycloak.Response)
}
switch {
case resp.StatusCode == http.StatusUnauthorized:
return keycloak.ClientDetailedError{HTTPStatus: http.StatusUnauthorized, Message: string(resp.Bytes())}
case resp.StatusCode >= 400:
return treatErrorStatus(resp)
case resp.StatusCode >= 200:
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("%s.%v", keycloak.MsgErrUnkownHTTPContentType, resp.Header.Get("Content-Type"))
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return keycloak.HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
}
switch resp.Header.Get("Content-Type") {
case "application/json":
return resp.JSON(data)
case "application/octet-stream":
_ = resp.Bytes()
return nil
default:
return fmt.Errorf("%s.%v", keycloak.MsgErrUnknownResponseStatusCode, resp.StatusCode)
return fmt.Errorf("%s.%v", keycloak.MsgErrUnkownHTTPContentType, resp.Header.Get("Content-Type"))
}
}
}
@ -140,7 +128,7 @@ func (c *Client) post(accessToken string, data interface{}, plugins ...plugin.Pl
var err error
var req = c.httpClient.Post()
req = applyPlugins(req, plugins...)
req, err = setAuthorisationAndHostHeaders(req, accessToken)
req = setAuthorisationHeader(req, accessToken)
if err != nil {
return "", err
@ -154,25 +142,23 @@ func (c *Client) post(accessToken string, data interface{}, plugins ...plugin.Pl
return "", errors.Wrap(err, keycloak.MsgErrCannotObtain+"."+keycloak.Response)
}
switch {
case resp.StatusCode == http.StatusUnauthorized:
return "", keycloak.ClientDetailedError{HTTPStatus: http.StatusUnauthorized, Message: string(resp.Bytes())}
case resp.StatusCode >= 400:
return "", treatErrorStatus(resp)
case resp.StatusCode >= 200:
var location = resp.Header.Get("Location")
switch resp.Header.Get("Content-Type") {
case "application/json":
return location, resp.JSON(data)
case "application/octet-stream":
data = resp.Bytes()
return location, nil
default:
return location, nil
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return "", keycloak.HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
}
var location = resp.Header.Get("Location")
switch resp.Header.Get("Content-Type") {
case "application/json":
return location, resp.JSON(data)
case "application/octet-stream":
data = resp.Bytes()
return location, nil
default:
return "", fmt.Errorf("%s.%v", keycloak.MsgErrUnknownResponseStatusCode, resp.StatusCode)
return location, nil
}
}
}
@ -181,7 +167,7 @@ func (c *Client) delete(accessToken string, plugins ...plugin.Plugin) error {
var err error
var req = c.httpClient.Delete()
req = applyPlugins(req, plugins...)
req, err = setAuthorisationAndHostHeaders(req, accessToken)
req = setAuthorisationHeader(req, accessToken)
if err != nil {
return err
@ -195,19 +181,14 @@ func (c *Client) delete(accessToken string, plugins ...plugin.Plugin) error {
return errors.Wrap(err, keycloak.MsgErrCannotObtain+"."+keycloak.Response)
}
switch {
case resp.StatusCode == http.StatusUnauthorized:
return keycloak.ClientDetailedError{HTTPStatus: http.StatusUnauthorized, Message: string(resp.Bytes())}
case resp.StatusCode >= 400:
return treatErrorStatus(resp)
case resp.StatusCode >= 200:
return nil
default:
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return keycloak.HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
}
return nil
}
}
@ -215,7 +196,7 @@ func (c *Client) put(accessToken string, plugins ...plugin.Plugin) error {
var err error
var req = c.httpClient.Put()
req = applyPlugins(req, plugins...)
req, err = setAuthorisationAndHostHeaders(req, accessToken)
req = setAuthorisationHeader(req, accessToken)
if err != nil {
return err
@ -229,35 +210,21 @@ func (c *Client) put(accessToken string, plugins ...plugin.Plugin) error {
return errors.Wrap(err, keycloak.MsgErrCannotObtain+"."+keycloak.Response)
}
switch {
case resp.StatusCode == http.StatusUnauthorized:
return keycloak.ClientDetailedError{HTTPStatus: http.StatusUnauthorized, Message: string(resp.Bytes())}
case resp.StatusCode >= 400:
return treatErrorStatus(resp)
case resp.StatusCode >= 200:
return nil
default:
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return keycloak.HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
}
}
}
func setAuthorisationAndHostHeaders(req *gentleman.Request, accessToken string) (*gentleman.Request, error) {
host, err := extractHostFromToken(accessToken)
if err != nil {
return req, err
return nil
}
}
func setAuthorisationHeader(req *gentleman.Request, accessToken string) *gentleman.Request {
var r = req.SetHeader("Authorization", fmt.Sprintf("Bearer %s", accessToken))
r = r.SetHeader("X-Forwarded-Proto", "https")
r.Context.Request.Host = host
return r, nil
return r
}
// applyPlugins apply all the plugins to the request req.
@ -269,41 +236,6 @@ func applyPlugins(req *gentleman.Request, plugins ...plugin.Plugin) *gentleman.R
return r
}
func extractHostFromToken(token string) (string, error) {
issuer, err := extractIssuerFromToken(token)
if err != nil {
return "", err
}
var u *url.URL
{
var err error
u, err = url.Parse(issuer)
if err != nil {
return "", errors.Wrap(err, keycloak.MsgErrCannotParse+"."+keycloak.TokenProviderURL)
}
}
return u.Host, nil
}
func extractIssuerFromToken(token string) (string, error) {
payload, _, err := jwt.Parse(token)
if err != nil {
return "", errors.Wrap(err, keycloak.MsgErrCannotParse+"."+keycloak.TokenMsg)
}
var jot Token
if err = jwt.Unmarshal(payload, &jot); err != nil {
return "", errors.Wrap(err, keycloak.MsgErrCannotUnmarshal+"."+keycloak.TokenMsg)
}
return jot.Issuer, nil
}
// createQueryPlugins create query parameters with the key values paramKV.
func createQueryPlugins(paramKV ...string) []plugin.Plugin {
var plugins = []plugin.Plugin{}
@ -314,70 +246,3 @@ func createQueryPlugins(paramKV ...string) []plugin.Plugin {
}
return plugins
}
func treatErrorStatus(resp *gentleman.Response) error {
var response map[string]interface{}
err := json.Unmarshal(resp.Bytes(), &response)
if message, ok := response["errorMessage"]; ok && err == nil {
return whitelistErrors(resp.StatusCode, message.(string))
}
return keycloak.HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
}
func whitelistErrors(statusCode int, message string) error {
// whitelist errors from Keycloak
reg := regexp.MustCompile("invalidPassword[a-zA-Z]*Message")
errorMessages := map[string]string{
"User exists with same username or email": keycloak.MsgErrExistingValue + "." + keycloak.UserOrEmail,
"usernameExistsMessage": keycloak.MsgErrExistingValue + "." + keycloak.UserOrEmail,
"emailExistsMessage": keycloak.MsgErrExistingValue + "." + keycloak.UserOrEmail,
"User exists with same username": keycloak.MsgErrExistingValue + "." + keycloak.Username,
"User exists with same email": keycloak.MsgErrExistingValue + "." + keycloak.Email,
"readOnlyUsernameMessage": keycloak.MsgErrReadOnly + "." + keycloak.Username,
}
switch {
//POST account/credentials/password with error message related to invalid value for the password
// of the format invalidPassword{a-zA-Z}*Message, e.g. invalidPasswordMinDigitsMessage
case reg.MatchString(message):
return commonhttp.Error{
Status: statusCode,
Message: "keycloak." + message,
}
// update account in back-office or self-service
case errorMessages[message] != "":
return commonhttp.Error{
Status: statusCode,
Message: "keycloak." + errorMessages[message],
}
default:
return keycloak.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.
type Token struct {
hdr *header
Issuer string `json:"iss,omitempty"`
Subject string `json:"sub,omitempty"`
ExpirationTime int64 `json:"exp,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
ID string `json:"jti,omitempty"`
Username string `json:"preferred_username,omitempty"`
}
type header struct {
Algorithm string `json:"alg,omitempty"`
KeyID string `json:"kid,omitempty"`
Type string `json:"typ,omitempty"`
ContentType string `json:"cty,omitempty"`
}

2
api/realm.go

@ -1,7 +1,7 @@
package api
import (
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)

2
api/recovery_code.go

@ -1,7 +1,7 @@
package api
import (
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/query"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)

2
api/roles.go

@ -1,7 +1,7 @@
package api
import (
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)

26
api/statistics.go

@ -1,26 +0,0 @@
package api
import (
"github.com/cloudtrust/keycloak-client/v3"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)
const (
statisticsPath = "/auth/realms/master/api/admin/realms/:realm/statistics"
statisticsUsers = statisticsPath + "/users"
statisticsCredentials = statisticsPath + "/credentials"
)
// GetStatisticsUsers returns statisctics on the total number of users and on their status
func (c *Client) GetStatisticsUsers(accessToken string, realmName string) (keycloak.StatisticsUsersRepresentation, error) {
var resp = keycloak.StatisticsUsersRepresentation{}
var err = c.get(accessToken, &resp, url.Path(statisticsUsers), url.Param("realm", realmName))
return resp, err
}
// GetStatisticsAuthenticators returns statistics on the authenticators used by the users on a certain realm
func (c *Client) GetStatisticsAuthenticators(accessToken string, realmName string) (map[string]int64, error) {
var resp = make(map[string]int64)
var err = c.get(accessToken, &resp, url.Path(statisticsCredentials), url.Param("realm", realmName))
return resp, err
}

2
api/users.go

@ -3,7 +3,7 @@ package api
import (
"errors"
"github.com/cloudtrust/keycloak-client/v3"
"github.com/nmasse-itix/keycloak-client"
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)

4
errormessages.go

@ -24,10 +24,6 @@ const (
TokenMsg = "token"
Response = "response"
AccessToken = "accessToken"
OIDCProvider = "OIDCProvider"
UserOrEmail = "UsernameOrEmail"
Username = "username"
Email = "email"
)
// HTTPError is returned when an error occured while contacting the keycloak instance.

3
go.mod

@ -1,9 +1,8 @@
module github.com/cloudtrust/keycloak-client/v3
module github.com/nmasse-itix/keycloak-client
go 1.15
require (
github.com/cloudtrust/common-service v2.3.2+incompatible
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gbrlsnchs/jwt/v2 v2.0.0
github.com/kr/pretty v0.1.0 // indirect

2
go.sum

@ -1,5 +1,3 @@
github.com/cloudtrust/common-service v2.3.2+incompatible h1:UpnaO5LjwsNjL3W8fQbZ03W5UrJZ8bT1mr91i0G7iKw=
github.com/cloudtrust/common-service v2.3.2+incompatible/go.mod h1:xvzyeNyD7L0x0xHn4Uqbpx/q9Z4tUT3gicALOAN40X4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

4
integration/integration-tests.go

@ -8,8 +8,8 @@ import (
"strings"
"time"
"github.com/cloudtrust/keycloak-client/v3"
api "github.com/cloudtrust/keycloak-client/v3/api"
"github.com/nmasse-itix/keycloak-client"
api "github.com/nmasse-itix/keycloak-client/api"
"github.com/spf13/pflag"
)

Loading…
Cancel
Save