Repository: Nerzal/gocloak
Pull request #117 (2019-12-12 16:52:35)

Adding CRUDL support with identity providers
Adding Create, Update, Delete Listing of identity providers

codecov[bot]: # [Codecov](https://codecov.io/gh/Nerzal/gocloak/pull/117?src=pr&el=h1) Report
> Merging [#117](https://codecov.io/gh/Nerzal/gocloak/pull/117?src=pr&el=desc) into [master](https://codecov.io/gh/Nerzal/gocloak/commit/91a456f20d2c5ccbe85ffb4f1f85bc8b2831f611?src=pr&el=desc) will **decrease** coverage by `2.86%`.
> The diff coverage is `71.87%`.

[![Impacted file tree graph](https://codecov.io/gh/Nerzal/gocloak/pull/117/graphs/tree.svg?width=650&token=gB52LSVNIV&height=150&src=pr)](https://codecov.io/gh/Nerzal/gocloak/pull/117?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #117      +/-   ##
==========================================
- Coverage   78.53%   75.66%   -2.87%     
==========================================
  Files           4        4              
  Lines         792      822      +30     
==========================================
  Hits          622      622              
- Misses        106      136      +30     
  Partials       64       64
```


| [Impacted Files](https://codecov.io/gh/Nerzal/gocloak/pull/117?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [models.go](https://codecov.io/gh/Nerzal/gocloak/pull/117/diff?src=pr&el=tree#diff-bW9kZWxzLmdv) | `73.68% <ø> (ø)` | :arrow_up: |
| [client.go](https://codecov.io/gh/Nerzal/gocloak/pull/117/diff?src=pr&el=tree#diff-Y2xpZW50Lmdv) | `74.83% <71.87%> (-3.14%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/Nerzal/gocloak/pull/117?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/Nerzal/gocloak/pull/117?src=pr&el=footer). Last update [91a456f...2423985](https://codecov.io/gh/Nerzal/gocloak/pull/117?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

SVilgelm: But you need to sync this branch with a master 

Patch:
diff --git a/client.go b/client.go
index 64e18e2..6f9cd52 100644
--- a/client.go
+++ b/client.go
@@ -1265,3 +1265,65 @@ func (client *gocloak) DeleteClientRoleFromUser(token string, realm string, clie
 
 	return checkForError(resp, err)
 }
+
+// ------------------
+// Identity Providers
+// ------------------
+
+// CreateIdentityProvider creates an identity provider in a realm
+func (client *gocloak) CreateIdentityProvider(token string, realm string, providerRep IdentityProviderRepresentation) (string, error) {
+	resp, err := client.getRequestWithBearerAuth(token).
+		SetBody(providerRep).
+		Post(client.getAdminRealmURL(realm, "identity-provider", "instances"))
+
+	if err := checkForError(resp, err); err != nil {
+		return "", err
+	}
+
+	return getID(resp), nil
+}
+
+// GetIdentityProviders returns list of identity providers in a realm
+func (client *gocloak) GetIdentityProviders(token string, realm string) ([]*IdentityProviderRepresentation, error) {
+	result := []*IdentityProviderRepresentation{}
+	resp, err := client.getRequestWithBearerAuth(token).
+		SetResult(&result).
+		Get(client.getAdminRealmURL(realm, "identity-provider", "instances"))
+
+	if err := checkForError(resp, err); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// GetIdentityProvider gets the identity provider in a realm
+func (client *gocloak) GetIdentityProvider(token string, realm string, alias string) (*IdentityProviderRepresentation, error) {
+	result := IdentityProviderRepresentation{}
+	resp, err := client.getRequestWithBearerAuth(token).
+		SetResult(&result).
+		Get(client.getAdminRealmURL(realm, "identity-provider", "instances", alias))
+
+	if err := checkForError(resp, err); err != nil {
+		return nil, err
+	}
+
+	return &result, nil
+}
+
+// UpdateIdentityProvider updates the identity provider in a realm
+func (client *gocloak) UpdateIdentityProvider(token string, realm string, alias string, providerRep IdentityProviderRepresentation) error {
+	resp, err := client.getRequestWithBearerAuth(token).
+		SetBody(providerRep).
+		Put(client.getAdminRealmURL(realm, "identity-provider", "instances", alias))
+
+	return checkForError(resp, err)
+}
+
+// DeleteIdentityProvider deletes the identity provider in a realm
+func (client *gocloak) DeleteIdentityProvider(token string, realm string, alias string) error {
+	resp, err := client.getRequestWithBearerAuth(token).
+		Delete(client.getAdminRealmURL(realm, "identity-provider", "instances", alias))
+
+	return checkForError(resp, err)
+}
diff --git a/gocloak.go b/gocloak.go
index 9c3b92a..5e08431 100644
--- a/gocloak.go
+++ b/gocloak.go
@@ -211,4 +211,16 @@ type GoCloak interface {
 	GetUserSessions(token, realm, userID string) ([]*UserSessionRepresentation, error)
 	// GetUserOfflineSessionsForClient returns offline sessions associated with the user and client
 	GetUserOfflineSessionsForClient(token, realm, userID, clientID string) ([]*UserSessionRepresentation, error)
+
+	// *** Identity Provider **
+	// CreateIdentityProvider creates an identity provider in a realm
+	CreateIdentityProvider(token string, realm string, providerRep IdentityProviderRepresentation) (string, error)
+	// GetIdentityProviders gets identity providers in a realm
+	GetIdentityProviders(token string, realm string) ([]*IdentityProviderRepresentation, error)
+	// GetIdentityProvider gets the identity provider in a realm
+	GetIdentityProvider(token string, realm string, alias string) (*IdentityProviderRepresentation, error)
+	// UpdateIdentityProvider updates the identity provider in a realm
+	UpdateIdentityProvider(token string, realm string, alias string, providerRep IdentityProviderRepresentation) error
+	// DeleteIdentityProvider deletes the identity provider in a realm
+	DeleteIdentityProvider(token string, realm string, alias string) error
 }
diff --git a/models.go b/models.go
index dee6a47..265a243 100644
--- a/models.go
+++ b/models.go
@@ -648,3 +648,19 @@ type ServerInfoRepesentation struct {
 	SystemInfo *SystemInfoRepresentation `json:"systemInfo,omitempty"`
 	MemoryInfo *MemoryInfoRepresentation `json:"memoryInfo"`
 }
+
+// IdentityProviderRepresentation represents an identity provider
+type IdentityProviderRepresentation struct {
+	AddReadTokenRoleOnCreate  *bool             `json:"addReadTokenRoleOnCreate,omitempty"`
+	Alias                     *string           `json:"alias,omitempty"`
+	Config                    map[string]string `json:"config,omitempty"`
+	DisplayName               *string           `json:"displayName,omitempty"`
+	Enabled                   *bool             `json:"enabled,omitempty"`
+	FirstBrokerLoginFlowAlias *string           `json:"firstBrokerLoginFlowAlias,omitempty"`
+	InternalID                *string           `json:"internalId,omitempty"`
+	LinkOnly                  *bool             `json:"linkOnly,omitempty"`
+	PostBrokerLoginFlowAlias  *string           `json:"postBrokerLoginFlowAlias,omitempty"`
+	ProviderID                *string           `json:"providerId,omitempty"`
+	StoreToken                *bool             `json:"storeToken,omitempty"`
+	TrustEmail                *bool             `json:"trustEmail,omitempty"`
+}


Test patch:
diff --git a/client_test.go b/client_test.go
index 3af43d2..609a0f3 100644
--- a/client_test.go
+++ b/client_test.go
@@ -2256,3 +2256,141 @@ func TestGocloak_CreateDeleteClientScopeWithMappers(t *testing.T) {
 	assert.EqualError(t, err, "404 Not Found: Could not find client scope")
 	assert.Nil(t, clientScopeActual, "client scope has not been deleted")
 }
+
+// -----------------
+// identity provider
+// -----------------
+
+func TestGocloak_CreateProvider(t *testing.T) {
+	t.Parallel()
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+	defer ClearRealmCache(t, client)
+
+	t.Run("create google provider", func(t *testing.T) {
+		repr := IdentityProviderRepresentation{
+			Alias:                     StringP("google"),
+			DisplayName:               StringP("Google"),
+			Enabled:                   BoolP(true),
+			ProviderID:                StringP("google"),
+			TrustEmail:                BoolP(true),
+			FirstBrokerLoginFlowAlias: StringP("first broker login"),
+			Config: map[string]string{
+				"clientId":     cfg.GoCloak.ClientID,
+				"clientSecret": cfg.GoCloak.ClientSecret,
+				"hostedDomain": "test.io",
+			},
+		}
+		provider, err := client.CreateIdentityProvider(token.AccessToken, cfg.GoCloak.Realm, repr)
+		assert.NoError(t, err)
+		assert.Equal(t, "google", provider)
+	})
+
+	t.Run("create azure provider", func(t *testing.T) {
+		repr := IdentityProviderRepresentation{
+			Alias:                     StringP("azure-oidc"),
+			DisplayName:               StringP("Azure"),
+			Enabled:                   BoolP(true),
+			ProviderID:                StringP("oidc"),
+			TrustEmail:                BoolP(true),
+			FirstBrokerLoginFlowAlias: StringP("first broker login"),
+			Config: map[string]string{
+				"clientId":         cfg.GoCloak.ClientID,
+				"clientSecret":     cfg.GoCloak.ClientSecret,
+				"authorizationUrl": "authorization-url",
+				"tokenUrl":         "token-url",
+				"logoutUrl":        "logout-url",
+				"userInfoUrl":      "userinfo-url",
+				"issuer":           "test-issuer",
+				"jwksUrl":          "jwks-url",
+			},
+		}
+		provider, err := client.CreateIdentityProvider(token.AccessToken, cfg.GoCloak.Realm, repr)
+		assert.NoError(t, err)
+		assert.Equal(t, "azure-oidc", provider)
+	})
+
+	t.Run("create OIDC V1.0 provider", func(t *testing.T) {
+		repr := IdentityProviderRepresentation{
+			Alias:                     StringP("oidc"),
+			DisplayName:               StringP("custom-oidc"),
+			Enabled:                   BoolP(true),
+			ProviderID:                StringP("oidc"),
+			TrustEmail:                BoolP(true),
+			FirstBrokerLoginFlowAlias: StringP("first broker login"),
+			Config: map[string]string{
+				"clientId":                 cfg.GoCloak.ClientID,
+				"clientSecret":             cfg.GoCloak.ClientSecret,
+				"authorizationUrl":         "authorization-url",
+				"tokenUrl":                 "token-url",
+				"logoutUrl":                "logout-url",
+				"userInfoUrl":              "userinfo-url",
+				"issuer":                   "test-issuer",
+				"loginHint":                "true",
+				"validateSignature":        "true",
+				"backchannelLogout":        "false",
+				"useJwksUrl":               "true",
+				"uiLocales":                "true",
+				"disableUserInfo":          "true",
+				"defaultScopes":            "default-scope",
+				"prompt":                   "false",
+				"allowedClockSkew":         "10",
+				"forwardedQueryParameters": "forwarded-query-parameters",
+			},
+		}
+		provider, err := client.CreateIdentityProvider(token.AccessToken, cfg.GoCloak.Realm, repr)
+		assert.NoError(t, err)
+		assert.Equal(t, "oidc", provider)
+	})
+
+	t.Run("Update google provider", func(t *testing.T) {
+		repr := IdentityProviderRepresentation{
+			Alias:                     StringP("google"),
+			DisplayName:               StringP("Google"),
+			Enabled:                   BoolP(true),
+			ProviderID:                StringP("google"),
+			TrustEmail:                BoolP(true),
+			FirstBrokerLoginFlowAlias: StringP("first broker login"),
+			Config: map[string]string{
+				"clientId":     cfg.GoCloak.ClientID,
+				"clientSecret": cfg.GoCloak.ClientSecret,
+				"hostedDomain": "updated-test.io",
+			},
+		}
+		err := client.UpdateIdentityProvider(token.AccessToken, cfg.GoCloak.Realm, "google", repr)
+		assert.NoError(t, err)
+
+		// listing identity providers here must now show three
+		providers, err := client.GetIdentityProviders(token.AccessToken, cfg.GoCloak.Realm)
+		assert.NoError(t, err)
+		assert.Equal(t, 3, len(providers))
+	})
+
+	t.Run("Delete google provider", func(t *testing.T) {
+		err := client.DeleteIdentityProvider(token.AccessToken, cfg.GoCloak.Realm, "google")
+		assert.NoError(t, err)
+	})
+
+	t.Run("List providers", func(t *testing.T) {
+		providers, err := client.GetIdentityProviders(token.AccessToken, cfg.GoCloak.Realm)
+		assert.NoError(t, err)
+		assert.Equal(t, 2, len(providers))
+	})
+
+	t.Run("Get Azure provider", func(t *testing.T) {
+		provider, err := client.GetIdentityProvider(token.AccessToken, cfg.GoCloak.Realm, "azure-oidc")
+		assert.NoError(t, err)
+		assert.Equal(t, "azure-oidc", *(provider.Alias))
+	})
+
+	t.Run("Delete Azure provider", func(t *testing.T) {
+		err := client.DeleteIdentityProvider(token.AccessToken, cfg.GoCloak.Realm, "azure-oidc")
+		assert.NoError(t, err)
+	})
+
+	t.Run("Delete OIDC V1.0 provider", func(t *testing.T) {
+		err := client.DeleteIdentityProvider(token.AccessToken, cfg.GoCloak.Realm, "oidc")
+		assert.NoError(t, err)
+	})
+}