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

5
Gopkg.toml

@ -32,3 +32,8 @@
[[constraint]] [[constraint]]
name = "gopkg.in/h2non/gentleman.v2" name = "gopkg.in/h2non/gentleman.v2"
version = "2.0.0" 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 // 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)) 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. // UpdateAuthenticationExecution update execution with new configuration.
func (c *Client) UpdateAuthenticationExecution(accessToken string, realmName, executionID string, authConfig AuthenticatorConfigRepresentation) error { 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. // LowerExecutionPriority lowers the execution’s priority.
func (c *Client) LowerExecutionPriority(accessToken string, realmName, executionID string) error { 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. // RaiseExecutionPriority raise the execution’s priority.
func (c *Client) RaiseExecutionPriority(accessToken string, realmName, executionID string) error { 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. // CreateAuthenticationFlow creates a new authentication flow.
func (c *Client) CreateAuthenticationFlow(accessToken string, realmName string, authFlow AuthenticationFlowRepresentation) error { 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. // 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. // 'newName' is the new name of the authentication flow.
func (c *Client) CopyExistingAuthenticationFlow(accessToken string, realmName, flowAlias, newName string) error { func (c *Client) CopyExistingAuthenticationFlow(accessToken string, realmName, flowAlias, newName string) error {
var m = map[string]string{"newName": newName} 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. // 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. // CreateAuthenticationExecutionForFlow add a new authentication execution to a flow.
// 'flowAlias' is the alias of the parent 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} 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. // CreateFlowWithExecutionForExistingFlow add a new flow with a new execution to an existing flow.
// 'flowAlias' is the alias of the parent authentication 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} 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. // 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. // RegisterRequiredAction register a new required action.
func (c *Client) RegisterRequiredAction(accessToken string, realmName, providerID, name string) error { func (c *Client) RegisterRequiredAction(accessToken string, realmName, providerID, name string) error {
var m = map[string]string{"providerId": providerID, "name": name} 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. // 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). // 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) { func (c *Client) GetKeyStore(accessToken string, realmName, idClient, attr string, keyStoreConfig KeyStoreConfig) ([]byte, error) {
var resp = []byte{} 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 return resp, err
} }
// GenerateCertificate generates a new certificate with new key pair. idClient is the id of client (not client-id). // 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) { func (c *Client) GenerateCertificate(accessToken string, realmName, idClient, attr string) (CertificateRepresentation, error) {
var resp = CertificateRepresentation{} 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 return resp, err
} }
// GenerateKeyPairAndCertificate generates a keypair and certificate and serves the private key in a specified keystore format. // 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) { func (c *Client) GenerateKeyPairAndCertificate(accessToken string, realmName, idClient, attr string, keyStoreConfig KeyStoreConfig) ([]byte, error) {
var resp = []byte{} 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 return resp, err
} }
// UploadCertificatePrivateKey uploads a certificate and eventually a private key. // UploadCertificatePrivateKey uploads a certificate and eventually a private key.
func (c *Client) UploadCertificatePrivateKey(accessToken string, realmName, idClient, attr string, file []byte) (CertificateRepresentation, error) { func (c *Client) UploadCertificatePrivateKey(accessToken string, realmName, idClient, attr string, file []byte) (CertificateRepresentation, error) {
var resp = CertificateRepresentation{} 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 return resp, err
} }
// UploadCertificate uploads only a certificate, not the private key. // UploadCertificate uploads only a certificate, not the private key.
func (c *Client) UploadCertificate(accessToken string, realmName, idClient, attr string, file []byte) (CertificateRepresentation, error) { func (c *Client) UploadCertificate(accessToken string, realmName, idClient, attr string, file []byte) (CertificateRepresentation, error) {
var resp = CertificateRepresentation{} 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 return resp, err
} }

2
client_initial_access.go

@ -12,7 +12,7 @@ const (
// CreateClientInitialAccess creates a new initial access token. // CreateClientInitialAccess creates a new initial access token.
func (c *Client) CreateClientInitialAccess(accessToken string, realmName string, access ClientInitialAccessCreatePresentation) (ClientInitialAccessPresentation, error) { func (c *Client) CreateClientInitialAccess(accessToken string, realmName string, access ClientInitialAccessCreatePresentation) (ClientInitialAccessPresentation, error) {
var resp = ClientInitialAccessPresentation{} 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 return resp, err
} }

28
client_role_mappings.go

@ -6,22 +6,30 @@ import (
) )
const ( 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. // AddClientRoleMapping add client-level roles to the user role mapping.
func (c *Client) CreateClientsRoleMapping(accessToken string, realmName, groupID, clientID string, roles []RoleRepresentation) error { func (c *Client) AddClientRolesToUserRoleMapping(accessToken string, realmName, userID, 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)) _, 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. // GetClientRoleMappings gets client-level role mappings for the user, and the app.
func (c *Client) GetClientsRoleMapping(accessToken string, realmName, groupID, clientID string) ([]RoleRepresentation, error) { func (c *Client) GetClientRoleMappings(accessToken string, realmName, userID, clientID string) ([]RoleRepresentation, error) {
var resp = []RoleRepresentation{} 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 return resp, err
} }
// DeleteClientsRoleMapping deletes client-level roles from user role mapping. // DeleteClientRolesFromUserRoleMapping deletes client-level roles from user role mapping.
func (c *Client) DeleteClientsRoleMapping(accessToken string, realmName, groupID, clientID string) error { 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", groupID), url.Param("client", clientID)) 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 { type UserRepresentation struct {
Access *map[string]interface{} `json:"access,omitempty"` Access *map[string]bool `json:"access,omitempty"`
Attributes *map[string]interface{} `json:"attributes,omitempty"` Attributes *map[string][]string `json:"attributes,omitempty"`
ClientConsents *[]UserConsentRepresentation `json:"clientConsents,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"` CreatedTimestamp *int64 `json:"createdTimestamp,omitempty"`
Credentials *[]CredentialRepresentation `json:"credentials,omitempty"` Credentials *[]CredentialRepresentation `json:"credentials,omitempty"`
DisableableCredentialTypes *[]string `json:"disableableCredentialTypes,omitempty"` DisableableCredentialTypes *[]string `json:"disableableCredentialTypes,omitempty"`

64
integration/integration.go

@ -15,6 +15,19 @@ const (
user = "version" 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() { func main() {
var conf = getKeycloakConfig() var conf = getKeycloakConfig()
var client, err = keycloak.New(*conf) var client, err = keycloak.New(*conf)
@ -28,6 +41,11 @@ func main() {
log.Fatalf("could not get access token: %v", err) 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 // Delete test realm
client.DeleteRealm(accessToken, tstRealm) client.DeleteRealm(accessToken, tstRealm)
@ -49,7 +67,8 @@ func main() {
// Create test realm. // Create test realm.
{ {
var realm = tstRealm var realm = tstRealm
var err = client.CreateRealm(accessToken, keycloak.RealmRepresentation{ var err error
_, err = client.CreateRealm(accessToken, keycloak.RealmRepresentation{
Realm: &realm, Realm: &realm,
}) })
if err != nil { if err != nil {
@ -111,7 +130,8 @@ func main() {
for _, u := range tstUsers { for _, u := range tstUsers {
var username = strings.ToLower(u.firstname + "." + u.lastname) var username = strings.ToLower(u.firstname + "." + u.lastname)
var email = username + "@cloudtrust.ch" 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, Username: &username,
FirstName: &u.firstname, FirstName: &u.firstname,
LastName: &u.lastname, LastName: &u.lastname,
@ -120,6 +140,7 @@ func main() {
if err != nil { if err != nil {
log.Fatalf("could not create test users: %v", err) log.Fatalf("could not create test users: %v", err)
} }
} }
// Check that all users where created. // Check that all users where created.
{ {
@ -145,6 +166,17 @@ func main() {
if len(users) != 50 { if len(users) != 50 {
log.Fatalf("there should be 50 users") 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. // email.
@ -207,6 +239,7 @@ func main() {
log.Fatalf("there should be 7 users matched by search") log.Fatalf("there should be 7 users matched by search")
} }
} }
fmt.Println("Test users retrieved.") 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 { 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() pflag.Parse()
return &keycloak.Config{ return &keycloak.Config{
Addr: *adr, AddrTokenProvider: *tokenAddr,
AddrAPI: *apiAddr,
Timeout: 10 * time.Second, Timeout: 10 * time.Second,
} }
} }

193
keycloak_client.go

@ -13,39 +13,63 @@ import (
"gopkg.in/h2non/gentleman.v2/plugin" "gopkg.in/h2non/gentleman.v2/plugin"
"gopkg.in/h2non/gentleman.v2/plugins/query" "gopkg.in/h2non/gentleman.v2/plugins/query"
"gopkg.in/h2non/gentleman.v2/plugins/timeout" "gopkg.in/h2non/gentleman.v2/plugins/timeout"
jwt "github.com/gbrlsnchs/jwt"
) )
// Config is the keycloak client http config. // Config is the keycloak client http config.
type Config struct { type Config struct {
Addr string AddrTokenProvider string
AddrAPI string
Timeout time.Duration Timeout time.Duration
} }
// Client is the keycloak client. // Client is the keycloak client.
type Client struct { type Client struct {
url *url.URL tokenProviderURL *url.URL
apiURL *url.URL
httpClient *gentleman.Client 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. // New returns a keycloak client.
func New(config Config) (*Client, error) { func New(config Config) (*Client, error) {
var u *url.URL var uToken *url.URL
{ {
var err error var err error
u, err = url.Parse(config.Addr) uToken, err = url.Parse(config.AddrTokenProvider)
if err != nil { 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() var httpClient = gentleman.New()
{ {
httpClient = httpClient.URL(u.String()) httpClient = httpClient.URL(uAPI.String())
httpClient = httpClient.Use(timeout.Request(config.Timeout)) httpClient = httpClient.Use(timeout.Request(config.Timeout))
} }
return &Client{ return &Client{
url: u, tokenProviderURL: uToken,
apiURL: uAPI,
httpClient: httpClient, httpClient: httpClient,
}, nil }, 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 return accessToken.(string), nil
} }
@ -98,7 +125,7 @@ func (c *Client) VerifyToken(realmName string, accessToken string) error {
var oidcProvider *oidc.Provider var oidcProvider *oidc.Provider
{ {
var err error 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) oidcProvider, err = oidc.NewProvider(context.Background(), issuer)
if err != nil { if err != nil {
return errors.Wrap(err, "could not create oidc provider") 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. // get is a HTTP get method.
func (c *Client) get(accessToken string, data interface{}, plugins ...plugin.Plugin) error { func (c *Client) get(accessToken string, data interface{}, plugins ...plugin.Plugin) error {
var err error
var req = c.httpClient.Get() 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 var resp *gentleman.Response
{ {
@ -128,9 +160,15 @@ func (c *Client) get(accessToken string, data interface{}, plugins ...plugin.Plu
switch { switch {
case resp.StatusCode == http.StatusUnauthorized: 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: 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: case resp.StatusCode >= 200:
switch resp.Header.Get("Content-Type") { switch resp.Header.Get("Content-Type") {
case "application/json": 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() 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 resp *gentleman.Response
{ {
var err error var err error
resp, err = req.Do() resp, err = req.Do()
if err != nil { if err != nil {
return errors.Wrap(err, "could not get response") return "", errors.Wrap(err, "could not get response")
} }
switch { switch {
case resp.StatusCode == http.StatusUnauthorized: 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: 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: case resp.StatusCode >= 200:
var location = resp.Header.Get("Location")
switch resp.Header.Get("Content-Type") { switch resp.Header.Get("Content-Type") {
case "application/json": case "application/json":
return resp.JSON(data) return location, resp.JSON(data)
case "application/octet-stream": case "application/octet-stream":
data = resp.Bytes() data = resp.Bytes()
return nil return location, nil
default: default:
return nil return location, nil
} }
default: 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 { func (c *Client) delete(accessToken string, plugins ...plugin.Plugin) error {
var err error
var req = c.httpClient.Delete() 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 var resp *gentleman.Response
{ {
@ -193,20 +252,35 @@ func (c *Client) delete(accessToken string, plugins ...plugin.Plugin) error {
switch { switch {
case resp.StatusCode == http.StatusUnauthorized: 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: 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: case resp.StatusCode >= 200:
return nil return nil
default: 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 { func (c *Client) put(accessToken string, plugins ...plugin.Plugin) error {
var err error
var req = c.httpClient.Put() 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 var resp *gentleman.Response
{ {
@ -218,26 +292,85 @@ func (c *Client) put(accessToken string, plugins ...plugin.Plugin) error {
switch { switch {
case resp.StatusCode == http.StatusUnauthorized: 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: 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: case resp.StatusCode >= 200:
return nil return nil
default: 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 setAuthorisationAndHostHeaders(req *gentleman.Request, accessToken string) (*gentleman.Request, error) {
func applyPlugins(req *gentleman.Request, accessToken string, plugins ...plugin.Plugin) *gentleman.Request { host, err := extractHostFromToken(accessToken)
if err != nil {
return req, err
}
var r = req.SetHeader("Authorization", fmt.Sprintf("Bearer %s", accessToken)) 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 { for _, p := range plugins {
r = r.Use(p) r = r.Use(p)
} }
return 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, "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. // createQueryPlugins create query parameters with the key values paramKV.
func createQueryPlugins(paramKV ...string) []plugin.Plugin { func createQueryPlugins(paramKV ...string) []plugin.Plugin {
var plugins = []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. // 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)) 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" userPath = "/auth/admin/realms/:realm/users"
userCountPath = userPath + "/count" userCountPath = userPath + "/count"
userIDPath = userPath + "/:id" userIDPath = userPath + "/:id"
resetPasswordPath = userIDPath + "/reset-password"
sendVerifyEmailPath = userIDPath + "/send-verify-email"
) )
// GetUsers returns a list of users, filtered according to the query parameters. // 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. // 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)) 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 { 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)) 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