Browse Source

Merge pull request #3 from cloudtrust/location_header

use Access Token for API calls
master
harture 7 years ago
committed by GitHub
parent
commit
a2556ede8f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 33
      Gopkg.lock
  2. 5
      Gopkg.toml
  3. 28
      authentication_management.go
  4. 10
      client_attribute_certificate.go
  5. 2
      client_initial_access.go
  6. 28
      client_role_mappings.go
  7. 6
      definitions.go
  8. 64
      integration/integration.go
  9. 193
      keycloak_client.go
  10. 2
      realm.go
  11. 38
      roles.go
  12. 20
      users.go

33
Gopkg.lock

@ -10,12 +10,12 @@
revision = "065b426bd41667456c1a924468f507673629c46b"
[[projects]]
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
digest = "1:0c07a9cb3d3c845439a4fcaae6c8bdd0e7727cbbd3acf1e032e5d4a2dc132306"
name = "github.com/gbrlsnchs/jwt"
packages = ["."]
pruneopts = ""
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
revision = "808efa0714baff8c25cc65ef8681966740beb9f9"
version = "v2.0.0"
[[projects]]
digest = "1:bcb38c8fc9b21bb8682ce2d605a7d4aeb618abc7f827e3ac0b27c0371fdb23fb"
@ -33,14 +33,6 @@
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = ""
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:386e12afcfd8964907c92dffd106860c0dedd71dbefae14397b77b724a13343b"
@ -60,17 +52,6 @@
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
version = "v1.0.0"
[[projects]]
digest = "1:a30066593578732a356dc7e5d7f78d69184ca65aeeff5939241a3ab10559bb06"
name = "github.com/stretchr/testify"
packages = [
"assert",
"require",
]
pruneopts = ""
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
version = "v1.2.1"
[[projects]]
branch = "master"
digest = "1:2ea6df0f542cc95a5e374e9cdd81eaa599ed0d55366eef92d2f6b9efa2795c07"
@ -185,11 +166,9 @@
analyzer-version = 1
input-imports = [
"github.com/coreos/go-oidc",
"github.com/davecgh/go-spew/spew",
"github.com/gbrlsnchs/jwt",
"github.com/pkg/errors",
"github.com/spf13/pflag",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/require",
"gopkg.in/h2non/gentleman.v2",
"gopkg.in/h2non/gentleman.v2/plugin",
"gopkg.in/h2non/gentleman.v2/plugins/body",

5
Gopkg.toml

@ -32,3 +32,8 @@
[[constraint]]
name = "gopkg.in/h2non/gentleman.v2"
version = "2.0.0"
[[constraint]]
name = "github.com/gbrlsnchs/jwt"
version = "2.0.0"

28
authentication_management.go

@ -48,7 +48,7 @@ func (c *Client) DeleteAuthenticatorConfig(accessToken string, realmName, config
}
// CreateAuthenticationExecution add new authentication execution
func (c *Client) CreateAuthenticationExecution(accessToken string, realmName string, authExec AuthenticationExecutionRepresentation) error {
func (c *Client) CreateAuthenticationExecution(accessToken string, realmName string, authExec AuthenticationExecutionRepresentation) (string, error) {
return c.post(accessToken, nil, url.Path(authenticationManagementPath+"/executions"), url.Param("realm", realmName), body.JSON(authExec))
}
@ -59,22 +59,26 @@ func (c *Client) DeleteAuthenticationExecution(accessToken string, realmName, ex
// UpdateAuthenticationExecution update execution with new configuration.
func (c *Client) UpdateAuthenticationExecution(accessToken string, realmName, executionID string, authConfig AuthenticatorConfigRepresentation) error {
return c.post(accessToken, nil, url.Path(authenticationManagementPath+"/executions/:id/config"), url.Param("realm", realmName), url.Param("id", executionID), body.JSON(authConfig))
_, err := c.post(accessToken, nil, url.Path(authenticationManagementPath+"/executions/:id/config"), url.Param("realm", realmName), url.Param("id", executionID), body.JSON(authConfig))
return err
}
// LowerExecutionPriority lowers the execution’s priority.
func (c *Client) LowerExecutionPriority(accessToken string, realmName, executionID string) error {
return c.post(accessToken, nil, url.Path(authenticationManagementPath+"/executions/:id/lower-priority"), url.Param("realm", realmName), url.Param("id", executionID))
_, err := c.post(accessToken, nil, url.Path(authenticationManagementPath+"/executions/:id/lower-priority"), url.Param("realm", realmName), url.Param("id", executionID))
return err
}
// RaiseExecutionPriority raise the execution’s priority.
func (c *Client) RaiseExecutionPriority(accessToken string, realmName, executionID string) error {
return c.post(accessToken, nil, url.Path(authenticationManagementPath+"/executions/:id/raise-priority"), url.Param("realm", realmName), url.Param("id", executionID))
_, err := c.post(accessToken, nil, url.Path(authenticationManagementPath+"/executions/:id/raise-priority"), url.Param("realm", realmName), url.Param("id", executionID))
return err
}
// CreateAuthenticationFlow creates a new authentication flow.
func (c *Client) CreateAuthenticationFlow(accessToken string, realmName string, authFlow AuthenticationFlowRepresentation) error {
return c.post(accessToken, nil, url.Path(authenticationManagementPath+"/flows"), url.Param("realm", realmName), body.JSON(authFlow))
_, err := c.post(accessToken, nil, url.Path(authenticationManagementPath+"/flows"), url.Param("realm", realmName), body.JSON(authFlow))
return err
}
// GetAuthenticationFlows returns a list of authentication flows.
@ -89,7 +93,8 @@ func (c *Client) GetAuthenticationFlows(accessToken string, realmName string) ([
// 'newName' is the new name of the authentication flow.
func (c *Client) CopyExistingAuthenticationFlow(accessToken string, realmName, flowAlias, newName string) error {
var m = map[string]string{"newName": newName}
return c.post(accessToken, nil, url.Path(authenticationManagementPath+"/flows/:flowAlias/copy"), url.Param("realm", realmName), url.Param("flowAlias", flowAlias), body.JSON(m))
_, err := c.post(accessToken, nil, url.Path(authenticationManagementPath+"/flows/:flowAlias/copy"), url.Param("realm", realmName), url.Param("flowAlias", flowAlias), body.JSON(m))
return err
}
// GetAuthenticationExecutionForFlow returns the authentication executions for a flow.
@ -106,16 +111,16 @@ func (c *Client) UpdateAuthenticationExecutionForFlow(accessToken string, realmN
// CreateAuthenticationExecutionForFlow add a new authentication execution to a flow.
// 'flowAlias' is the alias of the parent flow.
func (c *Client) CreateAuthenticationExecutionForFlow(accessToken string, realmName, flowAlias, provider string) error {
func (c *Client) CreateAuthenticationExecutionForFlow(accessToken string, realmName, flowAlias, provider string) (string, error) {
var m = map[string]string{"provider": provider}
return c.post(accessToken, url.Path(authenticationManagementPath+"/flows/:flowAlias/executions/execution"), url.Param("realm", realmName), url.Param("flowAlias", flowAlias), body.JSON(m))
return c.post(accessToken, nil, url.Path(authenticationManagementPath+"/flows/:flowAlias/executions/execution"), url.Param("realm", realmName), url.Param("flowAlias", flowAlias), body.JSON(m))
}
// 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(accessToken string, realmName, flowAlias, alias, flowType, provider, description string) error {
func (c *Client) CreateFlowWithExecutionForExistingFlow(accessToken string, realmName, flowAlias, alias, flowType, provider, description string) (string, error) {
var m = map[string]string{"alias": alias, "type": flowType, "provider": provider, "description": description}
return c.post(accessToken, url.Path(authenticationManagementPath+"/flows/:flowAlias/executions/flow"), url.Param("realm", realmName), url.Param("flowAlias", flowAlias), body.JSON(m))
return c.post(accessToken, nil, url.Path(authenticationManagementPath+"/flows/:flowAlias/executions/flow"), url.Param("realm", realmName), url.Param("flowAlias", flowAlias), body.JSON(m))
}
// GetAuthenticationFlow gets the authentication flow for id.
@ -154,7 +159,8 @@ func (c *Client) GetConfigDescriptionForClients(accessToken string, realmName st
// RegisterRequiredAction register a new required action.
func (c *Client) RegisterRequiredAction(accessToken string, realmName, providerID, name string) error {
var m = map[string]string{"providerId": providerID, "name": name}
return c.post(accessToken, url.Path(authenticationManagementPath+"/register-required-action"), url.Param("realm", realmName), body.JSON(m))
_, err := c.post(accessToken, nil, url.Path(authenticationManagementPath+"/register-required-action"), url.Param("realm", realmName), body.JSON(m))
return err
}
// GetRequiredActions returns a list of required actions.

10
client_attribute_certificate.go

@ -21,34 +21,34 @@ func (c *Client) GetKeyInfo(accessToken string, realmName, idClient, attr string
// 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(accessToken string, realmName, idClient, attr string, keyStoreConfig KeyStoreConfig) ([]byte, error) {
var resp = []byte{}
var err = c.post(accessToken, &resp, url.Path(clientAttrCertPath+"/download"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.JSON(keyStoreConfig))
_, err := c.post(accessToken, &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(accessToken string, realmName, idClient, attr string) (CertificateRepresentation, error) {
var resp = CertificateRepresentation{}
var err = c.post(accessToken, &resp, url.Path(clientAttrCertPath+"/generate"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr))
_, err := c.post(accessToken, &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(accessToken string, realmName, idClient, attr string, keyStoreConfig KeyStoreConfig) ([]byte, error) {
var resp = []byte{}
var err = c.post(accessToken, &resp, url.Path(clientAttrCertPath+"/generate-and-download"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.JSON(keyStoreConfig))
_, err := c.post(accessToken, &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(accessToken string, realmName, idClient, attr string, file []byte) (CertificateRepresentation, error) {
var resp = CertificateRepresentation{}
var err = c.post(accessToken, &resp, url.Path(clientAttrCertPath+"/upload"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.Reader(bytes.NewReader(file)))
_, err := c.post(accessToken, &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(accessToken string, realmName, idClient, attr string, file []byte) (CertificateRepresentation, error) {
var resp = CertificateRepresentation{}
var err = c.post(accessToken, &resp, url.Path(clientAttrCertPath+"/upload-certificate"), url.Param("realm", realmName), url.Param("id", idClient), url.Param("attr", attr), body.Reader(bytes.NewReader(file)))
_, err := c.post(accessToken, &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
}

2
client_initial_access.go

@ -12,7 +12,7 @@ const (
// CreateClientInitialAccess creates a new initial access token.
func (c *Client) CreateClientInitialAccess(accessToken string, realmName string, access ClientInitialAccessCreatePresentation) (ClientInitialAccessPresentation, error) {
var resp = ClientInitialAccessPresentation{}
var err = c.post(accessToken, &resp, url.Path(clientInitialAccessPath), url.Param("realm", realmName), body.JSON(access))
_, err := c.post(accessToken, &resp, nil, url.Path(clientInitialAccessPath), url.Param("realm", realmName), body.JSON(access))
return resp, err
}

28
client_role_mappings.go

@ -6,22 +6,30 @@ import (
)
const (
clientRoleMappingPath = "/auth/admin/realms/:realm/groups/:id/role-mappings/clients/:client"
clientRoleMappingPath = "/auth/admin/realms/:realm/users/:id/role-mappings/clients/:client"
realmRoleMappingPath = "/auth/admin/realms/:realm/users/:id/role-mappings/realm"
)
// CreateClientsRoleMapping add client-level roles to the user role mapping.
func (c *Client) CreateClientsRoleMapping(accessToken string, realmName, groupID, clientID string, roles []RoleRepresentation) error {
return c.post(accessToken, nil, url.Path(clientRoleMappingPath), url.Param("realm", realmName), url.Param("id", groupID), url.Param("client", clientID), body.JSON(roles))
// AddClientRoleMapping add client-level roles to the user role mapping.
func (c *Client) AddClientRolesToUserRoleMapping(accessToken string, realmName, userID, clientID string, roles []RoleRepresentation) error {
_, err := c.post(accessToken, nil, url.Path(clientRoleMappingPath), url.Param("realm", realmName), url.Param("id", userID), url.Param("client", clientID), body.JSON(roles))
return err
}
// GetClientsRoleMapping gets client-level role mappings for the user, and the app.
func (c *Client) GetClientsRoleMapping(accessToken string, realmName, groupID, clientID string) ([]RoleRepresentation, error) {
// GetClientRoleMappings gets client-level role mappings for the user, and the app.
func (c *Client) GetClientRoleMappings(accessToken string, realmName, userID, clientID string) ([]RoleRepresentation, error) {
var resp = []RoleRepresentation{}
var err = c.get(accessToken, &resp, url.Path(clientRoleMappingPath), url.Param("realm", realmName), url.Param("id", groupID), url.Param("client", clientID))
var err = c.get(accessToken, &resp, url.Path(clientRoleMappingPath), url.Param("realm", realmName), url.Param("id", userID), url.Param("client", clientID))
return resp, err
}
// DeleteClientsRoleMapping deletes client-level roles from user role mapping.
func (c *Client) DeleteClientsRoleMapping(accessToken string, realmName, groupID, clientID string) error {
return c.delete(accessToken, url.Path(clientRoleMappingPath), url.Param("realm", realmName), url.Param("id", groupID), url.Param("client", clientID))
// DeleteClientRolesFromUserRoleMapping deletes client-level roles from user role mapping.
func (c *Client) DeleteClientRolesFromUserRoleMapping(accessToken string, realmName, userID, clientID string) error {
return c.delete(accessToken, url.Path(clientRoleMappingPath), url.Param("realm", realmName), url.Param("id", userID), url.Param("client", clientID))
}
func (c *Client) GetRealmLevelRoleMappings(accessToken string, realmName, userID string) ([]RoleRepresentation, error) {
var resp = []RoleRepresentation{}
var err = c.get(accessToken, &resp, url.Path(realmRoleMappingPath), url.Param("realm", realmName), url.Param("id", userID))
return resp, err
}

6
definitions.go

@ -626,10 +626,10 @@ type UserFederationProviderRepresentation struct {
}
type UserRepresentation struct {
Access *map[string]interface{} `json:"access,omitempty"`
Attributes *map[string]interface{} `json:"attributes,omitempty"`
Access *map[string]bool `json:"access,omitempty"`
Attributes *map[string][]string `json:"attributes,omitempty"`
ClientConsents *[]UserConsentRepresentation `json:"clientConsents,omitempty"`
ClientRoles *map[string]interface{} `json:"clientRoles,omitempty"`
ClientRoles *map[string][]string `json:"clientRoles,omitempty"`
CreatedTimestamp *int64 `json:"createdTimestamp,omitempty"`
Credentials *[]CredentialRepresentation `json:"credentials,omitempty"`
DisableableCredentialTypes *[]string `json:"disableableCredentialTypes,omitempty"`

64
integration/integration.go

@ -15,6 +15,19 @@ const (
user = "version"
)
// This should be oncverted 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)
@ -28,6 +41,11 @@ func main() {
log.Fatalf("could not get access token: %v", err)
}
err = client.VerifyToken("master", accessToken)
if err != nil {
log.Fatalf("could not validate access token: %v", err)
}
// Delete test realm
client.DeleteRealm(accessToken, tstRealm)
@ -49,7 +67,8 @@ func main() {
// Create test realm.
{
var realm = tstRealm
var err = client.CreateRealm(accessToken, keycloak.RealmRepresentation{
var err error
_, err = client.CreateRealm(accessToken, keycloak.RealmRepresentation{
Realm: &realm,
})
if err != nil {
@ -111,7 +130,8 @@ func main() {
for _, u := range tstUsers {
var username = strings.ToLower(u.firstname + "." + u.lastname)
var email = username + "@cloudtrust.ch"
var err = client.CreateUser(accessToken, tstRealm, keycloak.UserRepresentation{
var err error
_, err = client.CreateUser(accessToken, tstRealm, keycloak.UserRepresentation{
Username: &username,
FirstName: &u.firstname,
LastName: &u.lastname,
@ -120,6 +140,7 @@ func main() {
if err != nil {
log.Fatalf("could not create test users: %v", err)
}
}
// Check that all users where created.
{
@ -145,6 +166,17 @@ func main() {
if len(users) != 50 {
log.Fatalf("there should be 50 users")
}
user, err := client.GetUser(accessToken, tstRealm, *(users[0].Id))
if err != nil {
log.Fatalf("could not get user")
}
if !(*(user.Username) != "") {
log.Fatalf("Username should not be empty")
}
fmt.Println("Test user retrieved.")
}
{
// email.
@ -207,6 +239,7 @@ func main() {
log.Fatalf("there should be 7 users matched by search")
}
}
fmt.Println("Test users retrieved.")
}
@ -315,33 +348,14 @@ func main() {
}
}
/*
// GetUser get the represention of the user.
func (c *Client) GetUser(realmName, userID string) (UserRepresentation, error) {
var resp = UserRepresentation{}
var err = c.get(&resp, url.Path(userIDPath), url.Param("realm", realmName), url.Param("id", userID))
return resp, err
}
// UpdateUser update 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))
}
// DeleteUser deletes the user.
func (c *Client) DeleteUser(realmName, userID string) error {
return c.delete(url.Path(userIDPath), url.Param("realm", realmName), url.Param("id", userID))
}
*/
func getKeycloakConfig() *keycloak.Config {
var adr = pflag.String("url", "http://localhost:8080", "keycloak address")
var apiAddr = pflag.String("urlKc", "http://localhost:8080", "keycloak address")
var tokenAddr = pflag.String("url", "http://localhost:8080", "token address")
pflag.Parse()
return &keycloak.Config{
Addr: *adr,
AddrTokenProvider: *tokenAddr,
AddrAPI: *apiAddr,
Timeout: 10 * time.Second,
}
}

193
keycloak_client.go

@ -13,39 +13,63 @@ import (
"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"
)
// Config is the keycloak client http config.
type Config struct {
Addr string
AddrTokenProvider string
AddrAPI string
Timeout time.Duration
}
// Client is the keycloak client.
type Client struct {
url *url.URL
tokenProviderURL *url.URL
apiURL *url.URL
httpClient *gentleman.Client
}
// HTTPError is returned when an error occured while contacting the keycloak instance.
type HTTPError struct {
HTTPStatus int
Message string
}
func (e HTTPError) Error() string {
return fmt.Sprintf("Error %d: %s", e.HTTPStatus, e.Message)
}
// New returns a keycloak client.
func New(config Config) (*Client, error) {
var u *url.URL
var uToken *url.URL
{
var err error
u, err = url.Parse(config.Addr)
uToken, err = url.Parse(config.AddrTokenProvider)
if err != nil {
return nil, errors.Wrap(err, "could not parse URL")
return nil, errors.Wrap(err, "could not parse Token Provider URL")
}
}
var uAPI *url.URL
{
var err error
uAPI, err = url.Parse(config.AddrAPI)
if err != nil {
return nil, errors.Wrap(err, "could not parse API URL")
}
}
var httpClient = gentleman.New()
{
httpClient = httpClient.URL(u.String())
httpClient = httpClient.URL(uAPI.String())
httpClient = httpClient.Use(timeout.Request(config.Timeout))
}
return &Client{
url: u,
tokenProviderURL: uToken,
apiURL: uAPI,
httpClient: httpClient,
}, nil
}
@ -90,6 +114,9 @@ func (c *Client) GetToken(realm string, username string, password string) (strin
}
}
fmt.Printf("%s", accessToken.(string))
fmt.Println()
return accessToken.(string), nil
}
@ -98,7 +125,7 @@ func (c *Client) VerifyToken(realmName string, accessToken string) error {
var oidcProvider *oidc.Provider
{
var err error
var issuer = fmt.Sprintf("%s/auth/realms/%s", c.url.String(), realmName)
var issuer = fmt.Sprintf("%s/auth/realms/%s", c.tokenProviderURL.String(), realmName)
oidcProvider, err = oidc.NewProvider(context.Background(), issuer)
if err != nil {
return errors.Wrap(err, "could not create oidc provider")
@ -114,9 +141,14 @@ func (c *Client) VerifyToken(realmName string, accessToken string) error {
// get is a HTTP get method.
func (c *Client) get(accessToken string, data interface{}, plugins ...plugin.Plugin) error {
var err error
var req = c.httpClient.Get()
req = applyPlugins(req, plugins...)
req, err = setAuthorisationAndHostHeaders(req, accessToken)
req = applyPlugins(req, accessToken, plugins...)
if err != nil {
return err
}
var resp *gentleman.Response
{
@ -128,9 +160,15 @@ func (c *Client) get(accessToken string, data interface{}, plugins ...plugin.Plu
switch {
case resp.StatusCode == http.StatusUnauthorized:
return fmt.Errorf("unauthorized request: '%v': %v", resp.RawResponse.Status, resp.String())
return HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
case resp.StatusCode >= 400:
return fmt.Errorf("invalid status code: '%v': %v", resp.RawResponse.Status, resp.String())
return HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
case resp.StatusCode >= 200:
switch resp.Header.Get("Content-Type") {
case "application/json":
@ -147,41 +185,62 @@ func (c *Client) get(accessToken string, data interface{}, plugins ...plugin.Plu
}
}
func (c *Client) post(accessToken string, data interface{}, plugins ...plugin.Plugin) error {
func (c *Client) post(accessToken string, data interface{}, plugins ...plugin.Plugin) (string, error) {
var err error
var req = c.httpClient.Post()
req = applyPlugins(req, accessToken, plugins...)
req = applyPlugins(req, plugins...)
req, err = setAuthorisationAndHostHeaders(req, accessToken)
if err != nil {
return "", err
}
var resp *gentleman.Response
{
var err error
resp, err = req.Do()
if err != nil {
return errors.Wrap(err, "could not get response")
return "", errors.Wrap(err, "could not get response")
}
switch {
case resp.StatusCode == http.StatusUnauthorized:
return fmt.Errorf("unauthorized request: '%v': %v", resp.RawResponse.Status, resp.String())
return "", HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
case resp.StatusCode >= 400:
return fmt.Errorf("invalid status code: '%v': %v", resp.RawResponse.Status, string(resp.Bytes()))
return "", HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
case resp.StatusCode >= 200:
var location = resp.Header.Get("Location")
switch resp.Header.Get("Content-Type") {
case "application/json":
return resp.JSON(data)
return location, resp.JSON(data)
case "application/octet-stream":
data = resp.Bytes()
return nil
return location, nil
default:
return nil
return location, nil
}
default:
return fmt.Errorf("unknown response status code: %v", resp.StatusCode)
return "", fmt.Errorf("unknown response status code: %v", resp.StatusCode)
}
}
}
func (c *Client) delete(accessToken string, plugins ...plugin.Plugin) error {
var err error
var req = c.httpClient.Delete()
req = applyPlugins(req, accessToken, plugins...)
req = applyPlugins(req, plugins...)
req, err = setAuthorisationAndHostHeaders(req, accessToken)
if err != nil {
return err
}
var resp *gentleman.Response
{
@ -193,20 +252,35 @@ func (c *Client) delete(accessToken string, plugins ...plugin.Plugin) error {
switch {
case resp.StatusCode == http.StatusUnauthorized:
return fmt.Errorf("unauthorized request: '%v': %v", resp.RawResponse.Status, resp.String())
return HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
case resp.StatusCode >= 400:
return fmt.Errorf("invalid status code: '%v': %v", resp.RawResponse.Status, string(resp.Bytes()))
return HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
case resp.StatusCode >= 200:
return nil
default:
return fmt.Errorf("unknown response status code: %v", resp.StatusCode)
return HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
}
}
}
func (c *Client) put(accessToken string, plugins ...plugin.Plugin) error {
var err error
var req = c.httpClient.Put()
req = applyPlugins(req, accessToken, plugins...)
req = applyPlugins(req, plugins...)
req, err = setAuthorisationAndHostHeaders(req, accessToken)
if err != nil {
return err
}
var resp *gentleman.Response
{
@ -218,26 +292,85 @@ func (c *Client) put(accessToken string, plugins ...plugin.Plugin) error {
switch {
case resp.StatusCode == http.StatusUnauthorized:
return fmt.Errorf("unauthorized request: '%v': %v", resp.RawResponse.Status, resp.String())
return HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
case resp.StatusCode >= 400:
return fmt.Errorf("invalid status code: '%v': %v", resp.RawResponse.Status, string(resp.Bytes()))
return HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
case resp.StatusCode >= 200:
return nil
default:
return fmt.Errorf("unknown response status code: %v", resp.StatusCode)
return HTTPError{
HTTPStatus: resp.StatusCode,
Message: string(resp.Bytes()),
}
}
}
}
// applyPlugins apply all the plugins to the request req.
func applyPlugins(req *gentleman.Request, accessToken string, plugins ...plugin.Plugin) *gentleman.Request {
func setAuthorisationAndHostHeaders(req *gentleman.Request, accessToken string) (*gentleman.Request, error) {
host, err := extractHostFromToken(accessToken)
if err != nil {
return req, err
}
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
}
// applyPlugins apply all the plugins to the request req.
func applyPlugins(req *gentleman.Request, plugins ...plugin.Plugin) *gentleman.Request {
var r = req
for _, p := range plugins {
r = r.Use(p)
}
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, "could not parse Token issuer URL")
}
}
return u.Host, nil
}
func extractIssuerFromToken(token string) (string, error) {
payload, _, err := jwt.Parse(token)
if err != nil {
return "", errors.Wrap(err, "could not parse Token")
}
var jot jwt.JWT
if err = jwt.Unmarshal(payload, &jot); err != nil {
return "", errors.Wrap(err, "could not unmarshall token")
}
return jot.Issuer, nil
}
// createQueryPlugins create query parameters with the key values paramKV.
func createQueryPlugins(paramKV ...string) []plugin.Plugin {
var plugins = []plugin.Plugin{}

2
realm.go

@ -20,7 +20,7 @@ func (c *Client) GetRealms(accessToken string) ([]RealmRepresentation, error){
}
// CreateRealm creates the realm from its RealmRepresentation.
func (c *Client) CreateRealm(accessToken string, realm RealmRepresentation) error {
func (c *Client) CreateRealm(accessToken string, realm RealmRepresentation) (string, error) {
return c.post(accessToken, nil, url.Path(realmRootPath), body.JSON(realm))
}

38
roles.go

@ -0,0 +1,38 @@
package keycloak
import (
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)
const (
rolePath = "/auth/admin/realms/:realm/roles"
roleByIDPath = "/auth/admin/realms/:realm/roles-by-id/:id"
clientRolePath = "/auth/admin/realms/:realm/clients/:id/roles"
)
// GetClientRoles gets all roles for the realm or client
func (c *Client) GetClientRoles(accessToken string, realmName, idClient string) ([]RoleRepresentation, error) {
var resp = []RoleRepresentation{}
var err = c.get(accessToken, &resp, url.Path(clientRolePath), url.Param("realm", realmName), url.Param("id", idClient))
return resp, err
}
// CreateClientRole creates a new role for the realm or client
func (c *Client) CreateClientRole(accessToken string, realmName, clientID string, role RoleRepresentation) (string, error) {
return c.post(accessToken, nil, url.Path(clientRolePath), url.Param("realm", realmName), url.Param("id", clientID), body.JSON(role))
}
// GetRoles gets all roles for the realm or client
func (c *Client) GetRoles(accessToken string, realmName string) ([]RoleRepresentation, error) {
var resp = []RoleRepresentation{}
var err = c.get(accessToken, &resp, url.Path(rolePath), url.Param("realm", realmName))
return resp, err
}
// GetRole gets a specific role’s representation
func (c *Client) GetRole(accessToken string, realmName string, roleID string) (RoleRepresentation, error) {
var resp = RoleRepresentation{}
var err = c.get(accessToken, &resp, url.Path(roleByIDPath), url.Param("realm", realmName), url.Param("id", roleID))
return resp, err
}

20
users.go

@ -11,6 +11,8 @@ const (
userPath = "/auth/admin/realms/:realm/users"
userCountPath = userPath + "/count"
userIDPath = userPath + "/:id"
resetPasswordPath = userIDPath + "/reset-password"
sendVerifyEmailPath = userIDPath + "/send-verify-email"
)
// GetUsers returns a list of users, filtered according to the query parameters.
@ -29,7 +31,7 @@ func (c *Client) GetUsers(accessToken string, realmName string, paramKV ...strin
}
// CreateUser creates the user from its UserRepresentation. The username must be unique.
func (c *Client) CreateUser(accessToken string, realmName string, user UserRepresentation) error {
func (c *Client) CreateUser(accessToken string, realmName string, user UserRepresentation) (string, error) {
return c.post(accessToken, nil, url.Path(userPath), url.Param("realm", realmName), body.JSON(user))
}
@ -56,3 +58,19 @@ func (c *Client) UpdateUser(accessToken string, realmName, userID string, user U
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 {
return fmt.Errorf("the number of key/val parameters should be even")
}
var plugins = append(createQueryPlugins(paramKV...), url.Path(sendVerifyEmailPath), url.Param("realm", realmName), url.Param("id", userID))
return c.put(accessToken, plugins...)
}

Loading…
Cancel
Save