mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-01-10 04:18:14 +02:00
Merge pull request #1210 from pb82/keycloak-oidc-provider
Keycloak oidc provider
This commit is contained in:
commit
526aff8c84
@ -8,6 +8,8 @@
|
|||||||
deserialization from v6.0.0 (only) has been removed to improve performance. If you are on v6.0.0, either upgrade
|
deserialization from v6.0.0 (only) has been removed to improve performance. If you are on v6.0.0, either upgrade
|
||||||
to a version before this first and allow legacy sessions to expire gracefully or change your `cookie-secret`
|
to a version before this first and allow legacy sessions to expire gracefully or change your `cookie-secret`
|
||||||
value and force all sessions to reauthenticate.
|
value and force all sessions to reauthenticate.
|
||||||
|
|
||||||
|
- [#1210](https://github.com/oauth2-proxy/oauth2-proxy/pull/1210) A new `keycloak-oidc` provider has been added with support for role based authentication. The existing keycloak auth provider will eventually be deprecated and removed. Please switch to the new provider `keycloak-oidc`.
|
||||||
|
|
||||||
## Breaking Changes
|
## Breaking Changes
|
||||||
|
|
||||||
@ -27,6 +29,7 @@
|
|||||||
- [#1142](https://github.com/oauth2-proxy/oauth2-proxy/pull/1142) Add pagewriter to upstream proxy (@JoelSpeed)
|
- [#1142](https://github.com/oauth2-proxy/oauth2-proxy/pull/1142) Add pagewriter to upstream proxy (@JoelSpeed)
|
||||||
- [#1181](https://github.com/oauth2-proxy/oauth2-proxy/pull/1181) Fix incorrect `cfg` name in show-debug-on-error flag (@iTaybb)
|
- [#1181](https://github.com/oauth2-proxy/oauth2-proxy/pull/1181) Fix incorrect `cfg` name in show-debug-on-error flag (@iTaybb)
|
||||||
- [#1207](https://github.com/oauth2-proxy/oauth2-proxy/pull/1207) Fix URI fragment handling on sign-in page, regression introduced in 7.1.0 (@tarvip)
|
- [#1207](https://github.com/oauth2-proxy/oauth2-proxy/pull/1207) Fix URI fragment handling on sign-in page, regression introduced in 7.1.0 (@tarvip)
|
||||||
|
- [#1210](https://github.com/oauth2-proxy/oauth2-proxy/pull/1210) New Keycloak OIDC Provider (@pb82)
|
||||||
- [#1244](https://github.com/oauth2-proxy/oauth2-proxy/pull/1244) Update Alpine image version to 3.14 (@ahovgaard)
|
- [#1244](https://github.com/oauth2-proxy/oauth2-proxy/pull/1244) Update Alpine image version to 3.14 (@ahovgaard)
|
||||||
|
|
||||||
|
|
||||||
|
@ -250,6 +250,7 @@ make up the header value
|
|||||||
| Field | Type | Description |
|
| Field | Type | Description |
|
||||||
| ----- | ---- | ----------- |
|
| ----- | ---- | ----------- |
|
||||||
| `groups` | _[]string_ | Group enables to restrict login to members of indicated group |
|
| `groups` | _[]string_ | Group enables to restrict login to members of indicated group |
|
||||||
|
| `roles` | _[]string_ | Role enables to restrict login to users with role (only available when using the keycloak-oidc provider) |
|
||||||
|
|
||||||
### LoginGovOptions
|
### LoginGovOptions
|
||||||
|
|
||||||
|
@ -146,12 +146,15 @@ If you are using GitHub enterprise, make sure you set the following to the appro
|
|||||||
|
|
||||||
### Keycloak Auth Provider
|
### Keycloak Auth Provider
|
||||||
|
|
||||||
1. Create new client in your Keycloak with **Access Type** 'confidental' and **Valid Redirect URIs** 'https://internal.yourcompany.com/oauth2/callback'
|
1. Create new client in your Keycloak realm with **Access Type** 'confidental' and **Valid Redirect URIs** 'https://internal.yourcompany.com/oauth2/callback'
|
||||||
2. Take note of the Secret in the credential tab of the client
|
2. Take note of the Secret in the credential tab of the client
|
||||||
3. Create a mapper with **Mapper Type** 'Group Membership' and **Token Claim Name** 'groups'.
|
3. Create a mapper with **Mapper Type** 'Group Membership' and **Token Claim Name** 'groups'.
|
||||||
|
|
||||||
|
:::note this is the legacy Keycloak Auth Prodiver, use `keycloak-oidc` if possible. :::
|
||||||
|
|
||||||
Make sure you set the following to the appropriate url:
|
Make sure you set the following to the appropriate url:
|
||||||
|
|
||||||
|
```
|
||||||
--provider=keycloak
|
--provider=keycloak
|
||||||
--client-id=<client you have created>
|
--client-id=<client you have created>
|
||||||
--client-secret=<your client's secret>
|
--client-secret=<your client's secret>
|
||||||
@ -161,6 +164,7 @@ Make sure you set the following to the appropriate url:
|
|||||||
--validate-url="http(s)://<keycloak host>/auth/realms/<your realm>/protocol/openid-connect/userinfo"
|
--validate-url="http(s)://<keycloak host>/auth/realms/<your realm>/protocol/openid-connect/userinfo"
|
||||||
--keycloak-group=<first_allowed_user_group>
|
--keycloak-group=<first_allowed_user_group>
|
||||||
--keycloak-group=<second_allowed_user_group>
|
--keycloak-group=<second_allowed_user_group>
|
||||||
|
```
|
||||||
|
|
||||||
For group based authorization, the optional `--keycloak-group` (legacy) or `--allowed-group` (global standard)
|
For group based authorization, the optional `--keycloak-group` (legacy) or `--allowed-group` (global standard)
|
||||||
flags can be used to specify which groups to limit access to.
|
flags can be used to specify which groups to limit access to.
|
||||||
@ -172,6 +176,25 @@ Keycloak userinfo endpoint response.
|
|||||||
The group management in keycloak is using a tree. If you create a group named admin in keycloak
|
The group management in keycloak is using a tree. If you create a group named admin in keycloak
|
||||||
you should define the 'keycloak-group' value to /admin.
|
you should define the 'keycloak-group' value to /admin.
|
||||||
|
|
||||||
|
### Keycloak OIDC Auth Provider
|
||||||
|
|
||||||
|
1. Create new client in your Keycloak realm with **Access Type** 'confidental', **Client protocol** 'openid-connect' and **Valid Redirect URIs** 'https://internal.yourcompany.com/oauth2/callback'
|
||||||
|
2. Take note of the Secret in the credential tab of the client
|
||||||
|
3. Create a mapper with **Mapper Type** 'Group Membership' and **Token Claim Name** 'groups'.
|
||||||
|
4. Create a mapper with **Mapper Type** 'Audience' and **Included Client Audience** and **Included Custom Audience** set to your client name.
|
||||||
|
|
||||||
|
Make sure you set the following to the appropriate url:
|
||||||
|
|
||||||
|
```
|
||||||
|
--provider=keycloak-oidc
|
||||||
|
--client-id=<your client's id>
|
||||||
|
--client-secret=<your client's secret>
|
||||||
|
--redirect-url=https://myapp.com/oauth2/callback
|
||||||
|
--oidc-issuer-url=https://<keycloak host>/auth/<your realm>/basic
|
||||||
|
--allowed-role=<realm role name> // Optional, required realm role
|
||||||
|
--allowed-role=<client id>:<client role name> // Optional, required client role
|
||||||
|
```
|
||||||
|
|
||||||
### GitLab Auth Provider
|
### GitLab Auth Provider
|
||||||
|
|
||||||
This auth provider has been tested against Gitlab version 12.X. Due to Gitlab API changes, it may not work for version prior to 12.X (see [994](https://github.com/oauth2-proxy/oauth2-proxy/issues/994)).
|
This auth provider has been tested against Gitlab version 12.X. Due to Gitlab API changes, it may not work for version prior to 12.X (see [994](https://github.com/oauth2-proxy/oauth2-proxy/issues/994)).
|
||||||
|
@ -192,6 +192,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
|
|||||||
| `--tls-key-file` | string | path to private key file | |
|
| `--tls-key-file` | string | path to private key file | |
|
||||||
| `--upstream` | string \| list | the http url(s) of the upstream endpoint, file:// paths for static files or `static://<status_code>` for static response. Routing is based on the path | |
|
| `--upstream` | string \| list | the http url(s) of the upstream endpoint, file:// paths for static files or `static://<status_code>` for static response. Routing is based on the path | |
|
||||||
| `--allowed-group` | string \| list | restrict logins to members of this group (may be given multiple times) | |
|
| `--allowed-group` | string \| list | restrict logins to members of this group (may be given multiple times) | |
|
||||||
|
| `--allowed-role` | string \| list | restrict logins to users with this role (may be given multiple times). Only works with the keycloak-oidc provider. | |
|
||||||
| `--validate-url` | string | Access token validation endpoint | |
|
| `--validate-url` | string | Access token validation endpoint | |
|
||||||
| `--version` | n/a | print version string | |
|
| `--version` | n/a | print version string | |
|
||||||
| `--whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` to allow subdomains (e.g. `.example.com`) \[[2](#footnote2)\] | |
|
| `--whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` to allow subdomains (e.g. `.example.com`) \[[2](#footnote2)\] | |
|
||||||
|
@ -508,6 +508,7 @@ type LegacyProvider struct {
|
|||||||
ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"` // Deprecated by OIDC 1.0
|
ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"` // Deprecated by OIDC 1.0
|
||||||
UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim"`
|
UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim"`
|
||||||
AllowedGroups []string `flag:"allowed-group" cfg:"allowed_groups"`
|
AllowedGroups []string `flag:"allowed-group" cfg:"allowed_groups"`
|
||||||
|
AllowedRoles []string `flag:"allowed-role" cfg:"allowed_roles"`
|
||||||
|
|
||||||
AcrValues string `flag:"acr-values" cfg:"acr_values"`
|
AcrValues string `flag:"acr-values" cfg:"acr_values"`
|
||||||
JWTKey string `flag:"jwt-key" cfg:"jwt_key"`
|
JWTKey string `flag:"jwt-key" cfg:"jwt_key"`
|
||||||
@ -563,6 +564,7 @@ func legacyProviderFlagSet() *pflag.FlagSet {
|
|||||||
|
|
||||||
flagSet.String("user-id-claim", providers.OIDCEmailClaim, "(DEPRECATED for `oidc-email-claim`) which claim contains the user ID")
|
flagSet.String("user-id-claim", providers.OIDCEmailClaim, "(DEPRECATED for `oidc-email-claim`) which claim contains the user ID")
|
||||||
flagSet.StringSlice("allowed-group", []string{}, "restrict logins to members of this group (may be given multiple times)")
|
flagSet.StringSlice("allowed-group", []string{}, "restrict logins to members of this group (may be given multiple times)")
|
||||||
|
flagSet.StringSlice("allowed-role", []string{}, "(keycloak-oidc) restrict logins to members of these roles (may be given multiple times)")
|
||||||
|
|
||||||
return flagSet
|
return flagSet
|
||||||
}
|
}
|
||||||
@ -656,6 +658,11 @@ func (l *LegacyProvider) convert() (Providers, error) {
|
|||||||
Token: l.GitHubToken,
|
Token: l.GitHubToken,
|
||||||
Users: l.GitHubUsers,
|
Users: l.GitHubUsers,
|
||||||
}
|
}
|
||||||
|
case "keycloak-oidc":
|
||||||
|
provider.KeycloakConfig = KeycloakOptions{
|
||||||
|
Groups: l.KeycloakGroups,
|
||||||
|
Roles: l.AllowedRoles,
|
||||||
|
}
|
||||||
case "keycloak":
|
case "keycloak":
|
||||||
provider.KeycloakConfig = KeycloakOptions{
|
provider.KeycloakConfig = KeycloakOptions{
|
||||||
Groups: l.KeycloakGroups,
|
Groups: l.KeycloakGroups,
|
||||||
|
@ -78,6 +78,9 @@ type Provider struct {
|
|||||||
type KeycloakOptions struct {
|
type KeycloakOptions struct {
|
||||||
// Group enables to restrict login to members of indicated group
|
// Group enables to restrict login to members of indicated group
|
||||||
Groups []string `json:"groups,omitempty"`
|
Groups []string `json:"groups,omitempty"`
|
||||||
|
|
||||||
|
// Role enables to restrict login to users with role (only available when using the keycloak-oidc provider)
|
||||||
|
Roles []string `json:"roles,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AzureOptions struct {
|
type AzureOptions struct {
|
||||||
|
@ -247,6 +247,11 @@ func parseProviderInfo(o *options.Options, msgs []string) []string {
|
|||||||
if len(o.Providers[0].KeycloakConfig.Groups) > 0 {
|
if len(o.Providers[0].KeycloakConfig.Groups) > 0 {
|
||||||
p.SetAllowedGroups(o.Providers[0].KeycloakConfig.Groups)
|
p.SetAllowedGroups(o.Providers[0].KeycloakConfig.Groups)
|
||||||
}
|
}
|
||||||
|
case *providers.KeycloakOIDCProvider:
|
||||||
|
if p.Verifier == nil {
|
||||||
|
msgs = append(msgs, "keycloak-oidc provider requires an oidc issuer URL")
|
||||||
|
}
|
||||||
|
p.AddAllowedRoles(o.Providers[0].KeycloakConfig.Roles)
|
||||||
case *providers.GoogleProvider:
|
case *providers.GoogleProvider:
|
||||||
if o.Providers[0].GoogleConfig.ServiceAccountJSON != "" {
|
if o.Providers[0].GoogleConfig.ServiceAccountJSON != "" {
|
||||||
file, err := os.Open(o.Providers[0].GoogleConfig.ServiceAccountJSON)
|
file, err := os.Open(o.Providers[0].GoogleConfig.ServiceAccountJSON)
|
||||||
|
142
providers/keycloak_oidc.go
Normal file
142
providers/keycloak_oidc.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
const keycloakOIDCProviderName = "Keycloak OIDC"
|
||||||
|
|
||||||
|
// KeycloakOIDCProvider creates a Keycloak provider based on OIDCProvider
|
||||||
|
type KeycloakOIDCProvider struct {
|
||||||
|
*OIDCProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeycloakOIDCProvider makes a KeycloakOIDCProvider using the ProviderData
|
||||||
|
func NewKeycloakOIDCProvider(p *ProviderData) *KeycloakOIDCProvider {
|
||||||
|
p.ProviderName = keycloakOIDCProviderName
|
||||||
|
return &KeycloakOIDCProvider{
|
||||||
|
OIDCProvider: &OIDCProvider{
|
||||||
|
ProviderData: p,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Provider = (*KeycloakOIDCProvider)(nil)
|
||||||
|
|
||||||
|
// AddAllowedRoles sets Keycloak roles that are authorized.
|
||||||
|
// Assumes `SetAllowedGroups` is already called on groups and appends to that
|
||||||
|
// with `role:` prefixed roles.
|
||||||
|
func (p *KeycloakOIDCProvider) AddAllowedRoles(roles []string) {
|
||||||
|
if p.AllowedGroups == nil {
|
||||||
|
p.AllowedGroups = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
for _, role := range roles {
|
||||||
|
p.AllowedGroups[formatRole(role)] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnrichSession is called after Redeem to allow providers to enrich session fields
|
||||||
|
// such as User, Email, Groups with provider specific API calls.
|
||||||
|
func (p *KeycloakOIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
|
||||||
|
err := p.OIDCProvider.EnrichSession(ctx, s)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not enrich oidc session: %v", err)
|
||||||
|
}
|
||||||
|
return p.extractRoles(ctx, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshSession adds role extraction logic to the refresh flow
|
||||||
|
func (p *KeycloakOIDCProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) {
|
||||||
|
refreshed, err := p.OIDCProvider.RefreshSession(ctx, s)
|
||||||
|
|
||||||
|
// Refresh could have failed or there was not session to refresh (with no error raised)
|
||||||
|
if err != nil || !refreshed {
|
||||||
|
return refreshed, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, p.extractRoles(ctx, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *KeycloakOIDCProvider) extractRoles(ctx context.Context, s *sessions.SessionState) error {
|
||||||
|
claims, err := p.getAccessClaims(ctx, s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var roles []string
|
||||||
|
roles = append(roles, claims.RealmAccess.Roles...)
|
||||||
|
roles = append(roles, getClientRoles(claims)...)
|
||||||
|
|
||||||
|
// Add to groups list with `role:` prefix to distinguish from groups
|
||||||
|
for _, role := range roles {
|
||||||
|
s.Groups = append(s.Groups, formatRole(role))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type realmAccess struct {
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type accessClaims struct {
|
||||||
|
RealmAccess realmAccess `json:"realm_access"`
|
||||||
|
ResourceAccess map[string]interface{} `json:"resource_access"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *KeycloakOIDCProvider) getAccessClaims(ctx context.Context, s *sessions.SessionState) (*accessClaims, error) {
|
||||||
|
// HACK: This isn't an ID Token, but has similar structure & signing
|
||||||
|
token, err := p.Verifier.Verify(ctx, s.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var claims *accessClaims
|
||||||
|
if err = token.Claims(&claims); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getClientRoles extracts client roles from the `resource_access` claim with
|
||||||
|
// the format `client:role`.
|
||||||
|
//
|
||||||
|
// ResourceAccess format:
|
||||||
|
// "resource_access": {
|
||||||
|
// "clientA": {
|
||||||
|
// "roles": [
|
||||||
|
// "roleA"
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// "clientB": {
|
||||||
|
// "roles": [
|
||||||
|
// "roleA",
|
||||||
|
// "roleB",
|
||||||
|
// "roleC"
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
func getClientRoles(claims *accessClaims) []string {
|
||||||
|
var clientRoles []string
|
||||||
|
for clientName, access := range claims.ResourceAccess {
|
||||||
|
accessMap, ok := access.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var roles interface{}
|
||||||
|
if roles, ok = accessMap["roles"]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, role := range roles.([]interface{}) {
|
||||||
|
clientRoles = append(clientRoles, fmt.Sprintf("%s:%s", clientName, role))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clientRoles
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatRole(role string) string {
|
||||||
|
return fmt.Sprintf("role:%s", role)
|
||||||
|
}
|
189
providers/keycloak_oidc_test.go
Normal file
189
providers/keycloak_oidc_test.go
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
package providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
accessTokenHeader = "ewogICJhbGciOiAiUlMyNTYiLAogICJ0eXAiOiAiSldUIgp9"
|
||||||
|
accessTokenPayload = "eyJyZWFsbV9hY2Nlc3MiOiB7InJvbGVzIjogWyJ3cml0ZSJdfSwgInJlc291cmNlX2FjY2VzcyI6IHsiZGVmYXVsdCI6IHsicm9sZXMiOiBbInJlYWQiXX19fQ"
|
||||||
|
accessTokenSignature = "dyt0CoTl4WoVjAHI9Q_CwSKhl6d_9rhM3NrXuJttkao"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DummyKeySet struct{}
|
||||||
|
|
||||||
|
func (DummyKeySet) VerifySignature(_ context.Context, _ string) (payload []byte, err error) {
|
||||||
|
p, _ := base64.RawURLEncoding.DecodeString(accessTokenPayload)
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAccessToken() string {
|
||||||
|
return fmt.Sprintf("%s.%s.%s", accessTokenHeader, accessTokenPayload, accessTokenSignature)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestKeycloakOIDCSetup() (*httptest.Server, *KeycloakOIDCProvider) {
|
||||||
|
redeemURL, server := newOIDCServer([]byte(fmt.Sprintf(`{"email": "new@thing.com", "expires_in": 300, "access_token": "%v"}`, getAccessToken())))
|
||||||
|
provider := newKeycloakOIDCProvider(redeemURL)
|
||||||
|
return server, provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func newKeycloakOIDCProvider(serverURL *url.URL) *KeycloakOIDCProvider {
|
||||||
|
p := NewKeycloakOIDCProvider(
|
||||||
|
&ProviderData{
|
||||||
|
LoginURL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "keycloak-oidc.com",
|
||||||
|
Path: "/oauth/auth"},
|
||||||
|
RedeemURL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "keycloak-oidc.com",
|
||||||
|
Path: "/oauth/token"},
|
||||||
|
ProfileURL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "keycloak-oidc.com",
|
||||||
|
Path: "/api/v3/user"},
|
||||||
|
ValidateURL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "keycloak-oidc.com",
|
||||||
|
Path: "/api/v3/user"},
|
||||||
|
Scope: "openid email profile"})
|
||||||
|
|
||||||
|
if serverURL != nil {
|
||||||
|
p.RedeemURL.Scheme = serverURL.Scheme
|
||||||
|
p.RedeemURL.Host = serverURL.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
keyset := DummyKeySet{}
|
||||||
|
p.Verifier = oidc.NewVerifier("", keyset, &oidc.Config{
|
||||||
|
ClientID: "client",
|
||||||
|
SkipIssuerCheck: true,
|
||||||
|
SkipClientIDCheck: true,
|
||||||
|
SkipExpiryCheck: true,
|
||||||
|
})
|
||||||
|
p.EmailClaim = "email"
|
||||||
|
p.GroupsClaim = "groups"
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("Keycloak OIDC Provider Tests", func() {
|
||||||
|
Context("New Provider Init", func() {
|
||||||
|
It("creates new keycloak oidc provider with expected defaults", func() {
|
||||||
|
p := newKeycloakOIDCProvider(nil)
|
||||||
|
providerData := p.Data()
|
||||||
|
Expect(providerData.ProviderName).To(Equal(keycloakOIDCProviderName))
|
||||||
|
Expect(providerData.LoginURL.String()).To(Equal("https://keycloak-oidc.com/oauth/auth"))
|
||||||
|
Expect(providerData.RedeemURL.String()).To(Equal("https://keycloak-oidc.com/oauth/token"))
|
||||||
|
Expect(providerData.ProfileURL.String()).To(Equal("https://keycloak-oidc.com/api/v3/user"))
|
||||||
|
Expect(providerData.ValidateURL.String()).To(Equal("https://keycloak-oidc.com/api/v3/user"))
|
||||||
|
Expect(providerData.Scope).To(Equal("openid email profile"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Allowed Roles", func() {
|
||||||
|
It("should prefix allowed roles and add them to groups", func() {
|
||||||
|
p := newKeycloakOIDCProvider(nil)
|
||||||
|
p.AddAllowedRoles([]string{"admin", "editor"})
|
||||||
|
Expect(p.AllowedGroups).To(HaveKey("role:admin"))
|
||||||
|
Expect(p.AllowedGroups).To(HaveKey("role:editor"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Enrich Session", func() {
|
||||||
|
It("should not fail when groups are not assigned", func() {
|
||||||
|
server, provider := newTestKeycloakOIDCSetup()
|
||||||
|
url, err := url.Parse(server.URL)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
provider.ProfileURL = url
|
||||||
|
|
||||||
|
existingSession := &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "a@b.com",
|
||||||
|
Groups: nil,
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: getAccessToken(),
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
}
|
||||||
|
expectedSession := &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "a@b.com",
|
||||||
|
Groups: []string{"role:write", "role:default:read"},
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: getAccessToken(),
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = provider.EnrichSession(context.Background(), existingSession)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(existingSession).To(Equal(expectedSession))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should add roles to existing groups", func() {
|
||||||
|
server, provider := newTestKeycloakOIDCSetup()
|
||||||
|
url, err := url.Parse(server.URL)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
provider.ProfileURL = url
|
||||||
|
|
||||||
|
existingSession := &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "a@b.com",
|
||||||
|
Groups: []string{"existing", "group"},
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: getAccessToken(),
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
}
|
||||||
|
expectedSession := &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "a@b.com",
|
||||||
|
Groups: []string{"existing", "group", "role:write", "role:default:read"},
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: getAccessToken(),
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = provider.EnrichSession(context.Background(), existingSession)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(existingSession).To(Equal(expectedSession))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Refresh Session", func() {
|
||||||
|
It("should refresh session and extract roles again", func() {
|
||||||
|
server, provider := newTestKeycloakOIDCSetup()
|
||||||
|
url, err := url.Parse(server.URL)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
provider.ProfileURL = url
|
||||||
|
|
||||||
|
existingSession := &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "a@b.com",
|
||||||
|
Groups: nil,
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: getAccessToken(),
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshed, err := provider.RefreshSession(context.Background(), existingSession)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(refreshed).To(BeTrue())
|
||||||
|
Expect(existingSession.ExpiresOn).ToNot(BeNil())
|
||||||
|
Expect(existingSession.CreatedAt).ToNot(BeNil())
|
||||||
|
Expect(existingSession.Groups).To(BeEquivalentTo([]string{"role:write", "role:default:read"}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -31,6 +31,8 @@ func New(provider string, p *ProviderData) Provider {
|
|||||||
return NewGitHubProvider(p)
|
return NewGitHubProvider(p)
|
||||||
case "keycloak":
|
case "keycloak":
|
||||||
return NewKeycloakProvider(p)
|
return NewKeycloakProvider(p)
|
||||||
|
case "keycloak-oidc":
|
||||||
|
return NewKeycloakOIDCProvider(p)
|
||||||
case "azure":
|
case "azure":
|
||||||
return NewAzureProvider(p)
|
return NewAzureProvider(p)
|
||||||
case "adfs":
|
case "adfs":
|
||||||
|
Loading…
Reference in New Issue
Block a user