Browse Source

Refactor keycloak client, add new routes.

master
Johan Droz 8 years ago
parent
commit
e478518e96
  1. 7
      .gitignore
  2. 2
      Gopkg.lock
  3. 268
      client/client.go
  4. 51
      client/client_test.go
  5. 147
      client/common.go
  6. 1150
      client/definitions.go
  7. 35
      client/realm.go
  8. 52
      client/realm_test.go
  9. 29
      client/realms.go
  10. 42
      client/user.go
  11. 67
      client/user_test.go
  12. 29
      client/users.go
  13. 2
      doc.go

7
.gitignore

@ -12,3 +12,10 @@
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
**/.DS_Store
.vscode/*
vendor/*
bin/*
**/debug
**/.coverprofile

2
Gopkg.lock

@ -46,6 +46,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "a3cc4fb82e0ff60bd971bed49992ef0a7aba2bfe6896479cd1b551afbcc17282"
inputs-digest = "08256d23e004781cdfb9661bb0d6d26ad54b41d4325097073183abc74cd1be4c"
solver-name = "gps-cdcl"
solver-version = 1

268
client/client.go

@ -0,0 +1,268 @@
package client
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
oidc "github.com/coreos/go-oidc"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/plugin"
"gopkg.in/h2non/gentleman.v2/plugins/timeout"
)
type HttpConfig struct {
Addr string
Username string
Password string
Timeout time.Duration
}
type client struct {
username string
password string
accessToken string
oidcProvider *oidc.Provider
httpClient *gentleman.Client
}
func New(config HttpConfig) (*client, error) {
var u *url.URL
{
var err error
u, err = url.Parse(config.Addr)
if err != nil {
return nil, fmt.Errorf("could not parse URL: %v", err)
}
}
if u.Scheme != "http" {
return nil, fmt.Errorf("protocol not supported, your address must start with http://, not %v", u.Scheme)
}
var httpClient = gentleman.New()
{
httpClient = httpClient.URL(u.String())
httpClient = httpClient.Use(timeout.Request(config.Timeout))
}
var oidcProvider *oidc.Provider
{
var err error
var issuer = fmt.Sprintf("%s/auth/realms/master", u.String())
oidcProvider, err = oidc.NewProvider(context.Background(), issuer)
if err != nil {
return nil, fmt.Errorf("could not create oidc provider: %v", err)
}
}
return &client{
username: config.Username,
password: config.Password,
oidcProvider: oidcProvider,
httpClient: httpClient,
}, nil
}
func (c *client) getToken() error {
var req *gentleman.Request
{
var authPath = "/auth/realms/master/protocol/openid-connect/token"
req = c.httpClient.Post()
req = req.SetHeader("Content-Type", "application/x-www-form-urlencoded")
req = req.Path(authPath)
req = req.Type("urlencoded")
req = req.BodyString(fmt.Sprintf("username=%s&password=%s&grant_type=password&client_id=admin-cli", c.username, c.password))
}
var resp *gentleman.Response
{
var err error
resp, err = req.Do()
if err != nil {
return fmt.Errorf("could not get token: %v", err)
}
}
defer resp.Close()
var unmarshalledBody map[string]interface{}
{
var err error
err = resp.JSON(&unmarshalledBody)
if err != nil {
return fmt.Errorf("could not unmarshal response: %v", err)
}
}
var accessToken interface{}
{
var ok bool
accessToken, ok = unmarshalledBody["access_token"]
if !ok {
return fmt.Errorf("could not find access token in response body")
}
}
c.accessToken = accessToken.(string)
return nil
}
func (c *client) verifyToken() error {
var v = c.oidcProvider.Verifier(&oidc.Config{SkipClientIDCheck: true})
var err error
_, err = v.Verify(context.Background(), c.accessToken)
return err
}
func (c *client) get(data interface{}, plugins ...plugin.Plugin) error {
var req = c.httpClient.Get()
req = applyPlugins(req, c.accessToken, plugins...)
var resp *gentleman.Response
{
var err error
resp, err = req.Do()
if err != nil {
return fmt.Errorf("could not get response: %v", err)
}
switch {
case resp.StatusCode == http.StatusUnauthorized:
// If the token is not valid (expired, ...) ask a new one.
if err = c.verifyToken(); err != nil {
var err = c.getToken()
if err != nil {
return fmt.Errorf("could not get token: %v", err)
}
}
return c.get(data, plugins...)
case resp.StatusCode >= 400:
return handleError(resp)
case resp.StatusCode >= 200:
return json.Unmarshal(resp.Bytes(), data)
default:
return fmt.Errorf("unknown response status code: %v", resp.StatusCode)
}
}
}
func (c *client) post(plugins ...plugin.Plugin) error {
var req = c.httpClient.Post()
req = applyPlugins(req, c.accessToken, plugins...)
var resp *gentleman.Response
{
var err error
resp, err = req.Do()
if err != nil {
return fmt.Errorf("could not get response: %v", err)
}
switch {
case resp.StatusCode == http.StatusUnauthorized:
// If the token is not valid (expired, ...) ask a new one.
if err = c.verifyToken(); err != nil {
var err = c.getToken()
if err != nil {
return fmt.Errorf("could not get token: %v", err)
}
}
return c.post(plugins...)
case resp.StatusCode >= 400:
return handleError(resp)
case resp.StatusCode >= 200:
return nil
default:
return fmt.Errorf("unknown response status code: %v", resp.StatusCode)
}
}
}
func (c *client) delete(plugins ...plugin.Plugin) error {
var req = c.httpClient.Delete()
req = applyPlugins(req, c.accessToken, plugins...)
var resp *gentleman.Response
{
var err error
resp, err = req.Do()
if err != nil {
return fmt.Errorf("could not get response: %v", err)
}
switch {
case resp.StatusCode == http.StatusUnauthorized:
// If the token is not valid (expired, ...) ask a new one.
if err = c.verifyToken(); err != nil {
var err = c.getToken()
if err != nil {
return fmt.Errorf("could not get token: %v", err)
}
}
return c.delete(plugins...)
case resp.StatusCode >= 400:
return handleError(resp)
case resp.StatusCode >= 200:
return nil
default:
return fmt.Errorf("unknown response status code: %v", resp.StatusCode)
}
}
}
func (c *client) put(plugins ...plugin.Plugin) error {
var req = c.httpClient.Put()
req = applyPlugins(req, c.accessToken, plugins...)
var resp *gentleman.Response
{
var err error
resp, err = req.Do()
if err != nil {
return fmt.Errorf("could not get response: %v", err)
}
switch {
case resp.StatusCode == http.StatusUnauthorized:
// If the token is not valid (expired, ...) ask a new one.
if err = c.verifyToken(); err != nil {
var err = c.getToken()
if err != nil {
return fmt.Errorf("could not get token: %v", err)
}
}
return c.put(plugins...)
case resp.StatusCode >= 400:
return handleError(resp)
case resp.StatusCode >= 200:
return nil
default:
return fmt.Errorf("unknown response status code: %v", resp.StatusCode)
}
}
}
func applyPlugins(req *gentleman.Request, accessToken string, plugins ...plugin.Plugin) *gentleman.Request {
var r = req.SetHeader("Authorization", fmt.Sprintf("Bearer %s", accessToken))
for _, p := range plugins {
r = r.Use(p)
}
return r
}
func handleError(resp *gentleman.Response) error {
var m = map[string]string{}
var err = json.Unmarshal(resp.Bytes(), &m)
if err != nil {
return fmt.Errorf("invalid status code: %v; could not unmarshal response: %v", resp.StatusCode, err)
}
return fmt.Errorf("error message: %v", m)
}
func str(s string) *string {
return &s
}

51
client/client_test.go

@ -1,55 +1,32 @@
package client
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
"fmt"
)
func initTest(t *testing.T) Client {
var config HttpConfig = HttpConfig{
Addr: "http://172.17.0.2:8080",
func initTest(t *testing.T) *client {
var config = HttpConfig{
Addr: "http://172.19.0.3:8080",
Username: "admin",
Password: "admin",
Timeout: time.Second * 5,
Timeout: time.Second * 20,
}
var client Client
var client *client
{
var err error
client, err = NewHttpClient(config)
require.Nil(t, err, "Failed to create client")
client, err = New(config)
require.Nil(t, err, "could not create client")
}
return client
}
func TestClient_getToken(t *testing.T) {
var genClient Client = initTest(t)
var httpClient *client = genClient.(*client)
var err error = httpClient.getToken()
require.Nil(t, err, "Failed to get token")
fmt.Println(httpClient.accessToken)
}
func TestClient_GetRealms(t *testing.T) {
var client Client = initTest(t)
var realms []RealmRepresentation
{
var err error
realms, err = client.GetRealms()
require.Nil(t, err, "Failed to get realms")
}
fmt.Println(realms)
}
func TestClient_GetUsers(t *testing.T) {
var client Client = initTest(t)
var users []UserRepresentation
{
var err error
users, err = client.GetUsers("master")
require.Nil(t, err, "Failed to get users")
}
fmt.Println(users[0])
func TestGetToken(t *testing.T) {
var client = initTest(t)
var err = client.getToken()
require.Nil(t, err, "could not get token")
fmt.Println(client.accessToken)
}

147
client/common.go

@ -1,147 +0,0 @@
package client
import (
"fmt"
"net/url"
"github.com/pkg/errors"
"time"
"net/http"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/plugin"
"gopkg.in/h2non/gentleman.v2/plugins/timeout"
//"gopkg.in/h2non/gentleman.v2/plugins/multipart"
)
type Client interface {
GetRealms() ([]RealmRepresentation, error)
GetUsers(realm string) ([]UserRepresentation, error)
}
type HttpConfig struct {
Addr string
Username string
Password string
Timeout time.Duration
}
type client struct {
username string
password string
accessToken string
httpClient *gentleman.Client
}
func NewHttpClient(config HttpConfig) (Client, error) {
var u *url.URL
{
var err error
u, err = url.Parse(config.Addr)
if err != nil {
return nil, errors.Wrap(err, "Parse failed")
}
}
if u.Scheme != "http" {
var m string = fmt.Sprintf("Unsupported protocol %s. Your address must start with http://", u.Scheme)
return nil, errors.New(m)
}
var httpClient *gentleman.Client = gentleman.New()
{
httpClient = httpClient.URL(u.String())
httpClient = httpClient.Use(timeout.Request(config.Timeout))
}
return &client{
username: config.Username,
password: config.Password,
httpClient: httpClient,
}, nil
}
func (c *client) getToken() error {
var req *gentleman.Request
{
var authPath string = "/auth/realms/master/protocol/openid-connect/token"
//var formData multipart.FormData = multipart.FormData{
// Data: map[string]multipart.Values{
// "username": multipart.Values{c.username},
// "password": multipart.Values{c.password},
// "grant_type": multipart.Values{"password"},
// "client_id": multipart.Values{"admin-cli"},
// },
//}
req = c.httpClient.Post()
req = req.SetHeader("Content-Type", "application/x-www-form-urlencoded")
req = req.Path(authPath)
req = req.Type("urlencoded")
req = req.BodyString(fmt.Sprintf("username=%s&password=%s&grant_type=password&client_id=admin-cli",c.username,c.password))
}
var resp *gentleman.Response
{
var err error
resp, err = req.Do()
if err != nil {
return errors.Wrap(err, "Failed to get the token response")
}
}
defer resp.Close()
var unmarshalledBody map[string]interface{}
{
var err error
err = resp.JSON(&unmarshalledBody)
if err != nil {
return errors.Wrap(err, "Failed to unmarshal response json")
}
}
var accessToken interface{}
{
var ok bool
accessToken, ok = unmarshalledBody["access_token"]
if !ok {
return errors.New("No access token in reponse body")
}
}
c.accessToken = accessToken.(string)
return nil
}
func (c *client) do(path string, plugins ...plugin.Plugin) (*gentleman.Response, error) {
var req *gentleman.Request = c.httpClient.Get()
{
req = req.Path(path)
req = req.SetHeader("Authorization", fmt.Sprintf("Bearer %s", c.accessToken))
for _, p := range plugins {
req = req.Use(p)
}
}
var resp *gentleman.Response
{
var err error
resp, err = req.Do()
if err != nil {
return nil, errors.Wrap(err, "Failed to get the response")
}
switch resp.StatusCode {
case http.StatusUnauthorized:
var err = c.getToken()
//This induces a potential infinite loop, where a new token gets requested and the
//process gets delayed so much it expires before the recursion.
//It is decided that should this happen, the machine would be considered to be in terrible shape
//and the loop wouldn't be the biggest problem.
if err != nil {
return nil, errors.Wrap(err, "Failed to get token")
}
return c.do(path)
default:
return resp, nil
}
}
}

1150
client/definitions.go

File diff suppressed because it is too large

35
client/realm.go

@ -0,0 +1,35 @@
package client
import (
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)
const (
realmRootPath = "/auth/admin/realms"
realmPath = realmRootPath + "/:realm"
)
func (c *client) GetRealms() ([]RealmRepresentation, error) {
var resp = []RealmRepresentation{}
var err = c.get(&resp, url.Path(realmRootPath))
return resp, err
}
func (c *client) CreateRealm(realm RealmRepresentation) error {
return c.post(url.Path(realmRootPath), body.JSON(realm))
}
func (c *client) GetRealm(realm string) (RealmRepresentation, error) {
var resp = RealmRepresentation{}
var err = c.get(&resp, url.Path(realmPath), url.Param("realm", realm))
return resp, err
}
func (c *client) UpdateRealm(realmName string, realm RealmRepresentation) error {
return c.put(url.Path(realmPath), url.Param("realm", realmName), body.JSON(realm))
}
func (c *client) DeleteRealm(realm string) error {
return c.delete(url.Path(realmPath), url.Param("realm", realm))
}

52
client/realm_test.go

@ -0,0 +1,52 @@
package client
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetRealms(t *testing.T) {
var client = initTest(t)
var realms []RealmRepresentation
{
var err error
realms, err = client.GetRealms()
require.Nil(t, err, "could not get realms")
assert.NotNil(t, realms)
}
}
func TestCreateRealm(t *testing.T) {
var client = initTest(t)
var realm = RealmRepresentation{
Realm: str("__internal"),
}
var err = client.CreateRealm(realm)
assert.Nil(t, err)
}
func TestGetRealm(t *testing.T) {
var client = initTest(t)
var realm, err = client.GetRealm("__internal")
assert.Nil(t, err)
assert.NotNil(t, realm)
}
func TestUpdateRealm(t *testing.T) {
var client = initTest(t)
var realm = RealmRepresentation{
DisplayName: str("Test realm"),
}
var err = client.UpdateRealm("__internal", realm)
assert.Nil(t, err)
}
func TestDeleteRealm(t *testing.T) {
var client = initTest(t)
var err = client.DeleteRealm("__internal")
assert.Nil(t, err)
}

29
client/realms.go

@ -1,29 +0,0 @@
package client
import (
"github.com/pkg/errors"
gentleman "gopkg.in/h2non/gentleman.v2"
"encoding/json"
)
func (c *client) GetRealms() ([]RealmRepresentation, error) {
var getRealms_Path string = "/auth/admin/realms"
var resp *gentleman.Response
{
var err error
resp, err = c.do(getRealms_Path)
if err != nil {
return nil, errors.Wrap(err, "Get Realms failed.")
}
}
var result []RealmRepresentation
{
var err error
err = json.Unmarshal(resp.Bytes(), &result)
if err != nil {
return nil, errors.Wrap(err, "Get Realms failed to unmarshal response.")
}
}
return result, nil
}

42
client/user.go

@ -0,0 +1,42 @@
package client
import (
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)
const (
userPath = "/auth/admin/realms/:realm/users"
userCountPath = userPath + "/count"
userIDPath = userPath + "/:id"
)
func (c *client) GetUsers(realm string) ([]UserRepresentation, error) {
var resp = []UserRepresentation{}
var err = c.get(&resp, url.Path(userPath), url.Param("realm", realm))
return resp, err
}
func (c *client) CreateUser(realm string, user UserRepresentation) error {
return c.post(url.Path(userPath), url.Param("realm", realm), body.JSON(user))
}
func (c *client) CountUsers(realm string) (int, error) {
var resp = 0
var err = c.get(&resp, url.Path(userCountPath), url.Param("realm", realm))
return resp, err
}
func (c *client) GetUser(realm, userID string) (UserRepresentation, error) {
var resp = UserRepresentation{}
var err = c.get(&resp, url.Path(userIDPath), url.Param("realm", realm), url.Param("id", userID))
return resp, err
}
func (c *client) UpdateUser(realm, userID string, user UserRepresentation) error {
return c.put(url.Path(userIDPath), url.Param("realm", realm), url.Param("id", userID), body.JSON(user))
}
func (c *client) DeleteUser(realm, userID string) error {
return c.delete(url.Path(userIDPath), url.Param("realm", realm), url.Param("id", userID))
}

67
client/user_test.go

@ -0,0 +1,67 @@
package client
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetUsers(t *testing.T) {
var client = initTest(t)
var users []UserRepresentation
{
var err error
users, err = client.GetUsers("__internal")
require.Nil(t, err, "could not get users")
}
for _, i := range users {
fmt.Println(*i.Username)
}
}
func TestCreateUser(t *testing.T) {
var client = initTest(t)
var realm = "__internal"
var user = UserRepresentation{
Username: str("johanr"),
}
var err = client.CreateUser(realm, user)
assert.Nil(t, err)
}
func TestCountUsers(t *testing.T) {
var client = initTest(t)
var realm = "__internal"
var count, err = client.CountUsers(realm)
assert.Nil(t, err)
assert.NotZero(t, count)
}
func TestGetUser(t *testing.T) {
var client = initTest(t)
var user UserRepresentation
{
var err error
user, err = client.GetUser("__internal", "078f735b-ac07-4b39-88cb-88647c4ff47c")
require.Nil(t, err, "could not get users")
}
fmt.Println(*user.Username)
}
func TestUpdateUser(t *testing.T) {
var client = initTest(t)
var user = UserRepresentation{
Email: str("john.doe@elca.ch"),
}
var err = client.UpdateUser("__internal", "078f735b-ac07-4b39-88cb-88647c4ff47c", user)
assert.Nil(t, err)
}
func TestDeleteUser(t *testing.T) {
var client = initTest(t)
var err = client.DeleteUser("__internal", "078f735b-ac07-4b39-88cb-88647c4ff47c")
assert.Nil(t, err)
}

29
client/users.go

@ -1,29 +0,0 @@
package client
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
"github.com/pkg/errors"
"encoding/json"
)
func (c *client)GetUsers(realm string) ([]UserRepresentation, error) {
var getUsers_Path string = fmt.Sprintf("/auth/admin/realms/%s/users", realm)
var resp *gentleman.Response
{
var err error
resp, err = c.do(getUsers_Path)
if err != nil {
return nil, errors.Wrap(err, "Get Realms failed.")
}
}
var result []UserRepresentation
{
var err error
err = json.Unmarshal(resp.Bytes(), &result)
if err != nil {
return nil, errors.Wrap(err, "Get Users failed to unmarshal response.")
}
}
return result, nil
}

2
doc.go

@ -0,0 +1,2 @@
// Keycloak client is a client for keycloak.
package keycloakclient
Loading…
Cancel
Save