feat: add Cidaas provider (#2273)
* Add sensible logging flag to default setup for logger * Fix default value flag for sensitive logging * Remove sensitive logging changes * Add Cidaas provider * Update CHANGELOG.md * Add required groups scope to defaults * Fix tests * Remove if block with protected resource * Fix linting * Adjust provider sorting, fixes * Directly handle error return Co-authored-by: Jan Larwig <jan@larwig.com> * Use less deep nesting Co-authored-by: Jan Larwig <jan@larwig.com> * Directly handle returned error Co-authored-by: Jan Larwig <jan@larwig.com> * Pass provider options to Cidaas provider Co-authored-by: Jan Larwig <jan@larwig.com> * Add import for provider options * Fix tests * Fix linting * Add Cidaas doc page * Add Cidaas provider doc page to overview * Fix link in docs * Fix link in docs * Add link to Cidaas * fix provider order in docs and changelog position Signed-off-by: Jan Larwig <jan@larwig.com> --------- Signed-off-by: Jan Larwig <jan@larwig.com> Co-authored-by: Teko012 <112829523+Teko012@users.noreply.github.com> Co-authored-by: Jan Larwig <jan@larwig.com> Co-authored-by: Kevin Kreitner <kevinkreitner@gmail.com>
This commit is contained in:
parent
9667bce094
commit
4c86a4d574
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
## Changes since v7.11.0
|
## Changes since v7.11.0
|
||||||
|
|
||||||
|
- [#2273](https://github.com/oauth2-proxy/oauth2-proxy/pull/2273) feat: add Cidaas provider (@Bibob7, @Teko012)
|
||||||
|
|
||||||
# V7.11.0
|
# V7.11.0
|
||||||
|
|
||||||
## Release Highlights
|
## Release Highlights
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
---
|
||||||
|
id: cidaas
|
||||||
|
title: Cidaas
|
||||||
|
---
|
||||||
|
|
||||||
|
[Cidaas](https://www.cidaas.com/) is an Identity as a Service (IDaaS) solution that provides authentication and authorization services.
|
||||||
|
It supports various protocols including OpenID Connect, OAuth 2.0, and SAML.
|
||||||
|
|
||||||
|
However, Cidaas provides groups and their roles as hierarchical claims, which are not supported by oauth2-proxy yet.
|
||||||
|
The Cidaas provider transforms the hierarchical claims into a flat list of groups, which can be used by oauth2-proxy.
|
||||||
|
|
||||||
|
Example of groups and roles in Cidaas:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"groupId": "group1",
|
||||||
|
"roles": ["role1", "role2"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupId": "group2",
|
||||||
|
"roles": ["role3"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This will be transformed into a flat list of groups:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"groups": ["group1:role1", "group2:role2", "group2:role3"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Apart from that the Cidaas provider inherits all the features of the [OpenID Connect provider](openid_connect.md).
|
||||||
|
|
@ -10,6 +10,7 @@ Valid providers are :
|
||||||
|
|
||||||
- [ADFS](adfs.md)
|
- [ADFS](adfs.md)
|
||||||
- [Bitbucket](bitbucket.md)
|
- [Bitbucket](bitbucket.md)
|
||||||
|
- [Cidaas](cidaas.md)
|
||||||
- [DigitalOcean](digitalocean.md)
|
- [DigitalOcean](digitalocean.md)
|
||||||
- [Facebook](facebook.md)
|
- [Facebook](facebook.md)
|
||||||
- [Gitea](gitea.md)
|
- [Gitea](gitea.md)
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,9 @@ const (
|
||||||
// BitbucketProvider is the provider type for Bitbucket
|
// BitbucketProvider is the provider type for Bitbucket
|
||||||
BitbucketProvider ProviderType = "bitbucket"
|
BitbucketProvider ProviderType = "bitbucket"
|
||||||
|
|
||||||
|
// CidaasProvider is the provider type for Cidaas IDP
|
||||||
|
CidaasProvider ProviderType = "cidaas"
|
||||||
|
|
||||||
// DigitalOceanProvider is the provider type for DigitalOcean
|
// DigitalOceanProvider is the provider type for DigitalOcean
|
||||||
DigitalOceanProvider ProviderType = "digitalocean"
|
DigitalOceanProvider ProviderType = "digitalocean"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
package providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/bitly/go-simplejson"
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GroupsClaimList []GroupClaimEntry
|
||||||
|
|
||||||
|
type GroupClaimEntry struct {
|
||||||
|
GroupID string `json:"groupId"`
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CIDAASProvider represents an CIDAAS based Identity Provider
|
||||||
|
type CIDAASProvider struct {
|
||||||
|
*OIDCProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Provider = (*CIDAASProvider)(nil)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CidaasProviderName = "CIDAAS"
|
||||||
|
CidaasGroupName = "cidaas"
|
||||||
|
CidaasDefaultScope = "openid email profile roles groups"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewCIDAASProvider initiates a new CIDAASProvider
|
||||||
|
func NewCIDAASProvider(p *ProviderData, opts options.Provider) *CIDAASProvider {
|
||||||
|
p.setProviderDefaults(providerDefaults{
|
||||||
|
name: CidaasProviderName,
|
||||||
|
scope: CidaasDefaultScope,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &CIDAASProvider{
|
||||||
|
OIDCProvider: NewOIDCProvider(p, opts.OIDCConfig),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshSession uses the RefreshToken to fetch new Access and ID Tokens
|
||||||
|
func (p *CIDAASProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) {
|
||||||
|
if s == nil || s.RefreshToken == "" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.redeemRefreshToken(ctx, s); err != nil {
|
||||||
|
return false, fmt.Errorf("unable to redeem refresh token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.EnrichSession(ctx, s); err != nil {
|
||||||
|
return false, fmt.Errorf("unable to enrich session data after refresh: %w %v", err, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnrichSession data to add email an groups
|
||||||
|
func (p *CIDAASProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
|
||||||
|
if p.ProfileURL.String() == "" && s.Email == "" {
|
||||||
|
return errors.New("id_token did not contain an email and profileURL is not defined")
|
||||||
|
} else if p.ProfileURL.String() == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get missing emails or groups from a profileURL
|
||||||
|
if err := p.enrichFromUserinfoEndpoint(ctx, s); err != nil {
|
||||||
|
logger.Errorf("Warning: Profile URL request failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a mandatory email wasn't set, error at this point.
|
||||||
|
if s.Email == "" {
|
||||||
|
return errors.New("neither the id_token nor the profileURL set an email")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// enrichFromUserinfoEndpoint enriches a session's Email & Groups via the JSON response of
|
||||||
|
// an OIDC profile URL
|
||||||
|
func (p *CIDAASProvider) enrichFromUserinfoEndpoint(ctx context.Context, s *sessions.SessionState) error {
|
||||||
|
// profile url is userinfo url in case of Cidaas
|
||||||
|
respJSON, err := requests.New(p.ProfileURL.String()).
|
||||||
|
WithContext(ctx).
|
||||||
|
WithHeaders(makeOIDCHeader(s.AccessToken)).
|
||||||
|
Do().
|
||||||
|
UnmarshalSimpleJSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
email, err := respJSON.Get(p.EmailClaim).String()
|
||||||
|
if err == nil && s.Email == "" {
|
||||||
|
s.Email = email
|
||||||
|
}
|
||||||
|
|
||||||
|
groups, err := p.extractGroups(respJSON)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("extracting groups failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Groups = groups
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CIDAASProvider) extractGroups(respJSON *simplejson.Json) ([]string, error) {
|
||||||
|
rawGroupsClaim, err := respJSON.Get(p.GroupsClaim).MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupsClaimList GroupsClaimList
|
||||||
|
err = json.Unmarshal(rawGroupsClaim, &groupsClaimList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var groups []string
|
||||||
|
for _, group := range groupsClaimList {
|
||||||
|
for _, role := range group.Roles {
|
||||||
|
groups = append(groups, fmt.Sprintf("%s:%s", group.GroupID, role))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cidaas specific roles
|
||||||
|
if rolesVal, rolesClaimExists := respJSON.CheckGet("roles"); rolesClaimExists {
|
||||||
|
cidaasRoles, err := rolesVal.StringArray()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal roles failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, role := range cidaasRoles {
|
||||||
|
groups = append(groups, fmt.Sprintf("%s:%s", CidaasGroupName, role))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,493 @@
|
||||||
|
package providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newCidaasProvider(serverURL *url.URL) *CIDAASProvider {
|
||||||
|
providerData := &ProviderData{
|
||||||
|
ProviderName: "cidaas",
|
||||||
|
ClientID: oidcClientID,
|
||||||
|
ClientSecret: oidcSecret,
|
||||||
|
LoginURL: &url.URL{
|
||||||
|
Scheme: serverURL.Scheme,
|
||||||
|
Host: serverURL.Host,
|
||||||
|
Path: "/login/oauth/authorize"},
|
||||||
|
RedeemURL: &url.URL{
|
||||||
|
Scheme: serverURL.Scheme,
|
||||||
|
Host: serverURL.Host,
|
||||||
|
Path: "/login/oauth/access_token"},
|
||||||
|
ProfileURL: &url.URL{
|
||||||
|
Scheme: serverURL.Scheme,
|
||||||
|
Host: serverURL.Host,
|
||||||
|
Path: "/profile"},
|
||||||
|
ValidateURL: &url.URL{
|
||||||
|
Scheme: serverURL.Scheme,
|
||||||
|
Host: serverURL.Host,
|
||||||
|
Path: "/api"},
|
||||||
|
Scope: "openid profile offline_access roles groups",
|
||||||
|
EmailClaim: "email",
|
||||||
|
GroupsClaim: "groups",
|
||||||
|
Verifier: oidc.NewVerifier(
|
||||||
|
oidcIssuer,
|
||||||
|
mockJWKS{},
|
||||||
|
&oidc.Config{ClientID: oidcClientID},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
cfg := options.Provider{
|
||||||
|
Type: options.CidaasProvider,
|
||||||
|
}
|
||||||
|
|
||||||
|
p := NewCIDAASProvider(providerData, cfg)
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCidaasServer(pathBodyMap map[string][]byte) (*url.URL, *httptest.Server) {
|
||||||
|
s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
body, ok := pathBodyMap[r.URL.Path]
|
||||||
|
if !ok {
|
||||||
|
rw.WriteHeader(404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rw.Header().Add("content-type", "application/json")
|
||||||
|
_, _ = rw.Write(body)
|
||||||
|
}))
|
||||||
|
u, _ := url.Parse(s.URL)
|
||||||
|
return u, s
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestCidaasSetup(pathToBodyMap map[string][]byte) (*httptest.Server, *CIDAASProvider) {
|
||||||
|
redeemURL, server := newCidaasServer(pathToBodyMap)
|
||||||
|
provider := newCidaasProvider(redeemURL)
|
||||||
|
return server, provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCidaasProvider_EnrichSession(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
ExistingSession *sessions.SessionState
|
||||||
|
EmailClaim string
|
||||||
|
GroupsClaim string
|
||||||
|
ProfileJSON map[string]interface{}
|
||||||
|
ExpectedError error
|
||||||
|
ExpectedSession *sessions.SessionState
|
||||||
|
}{
|
||||||
|
"Missing Email Only in Profile URL": {
|
||||||
|
ExistingSession: &sessions.SessionState{
|
||||||
|
User: "missing.email",
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
EmailClaim: "email",
|
||||||
|
GroupsClaim: "groups",
|
||||||
|
ProfileJSON: map[string]interface{}{
|
||||||
|
"email": "found@email.com",
|
||||||
|
},
|
||||||
|
ExpectedError: nil,
|
||||||
|
ExpectedSession: &sessions.SessionState{
|
||||||
|
User: "missing.email",
|
||||||
|
Email: "found@email.com",
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Missing Email with Custom Claim": {
|
||||||
|
ExistingSession: &sessions.SessionState{
|
||||||
|
User: "missing.email",
|
||||||
|
Groups: []string{"already", "populated"},
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
EmailClaim: "weird",
|
||||||
|
GroupsClaim: "groups",
|
||||||
|
ProfileJSON: map[string]interface{}{
|
||||||
|
"weird": "weird@claim.com",
|
||||||
|
"groups": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"groupId": "CIDAAS_USERS",
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
ExpectedError: nil,
|
||||||
|
ExpectedSession: &sessions.SessionState{
|
||||||
|
User: "missing.email",
|
||||||
|
Email: "weird@claim.com",
|
||||||
|
Groups: []string{"CIDAAS_USERS:USER", "cidaas:USER"},
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Missing Email not in Profile URL": {
|
||||||
|
ExistingSession: &sessions.SessionState{
|
||||||
|
User: "missing.email",
|
||||||
|
Groups: []string{"already", "populated"},
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
EmailClaim: "email",
|
||||||
|
GroupsClaim: "groups",
|
||||||
|
ProfileJSON: map[string]interface{}{
|
||||||
|
"groups": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"groupId": "CIDAAS_USERS",
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
ExpectedError: errors.New("neither the id_token nor the profileURL set an email"),
|
||||||
|
ExpectedSession: &sessions.SessionState{
|
||||||
|
User: "missing.email",
|
||||||
|
Groups: []string{"CIDAAS_USERS:USER", "cidaas:USER"},
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Missing Groups": {
|
||||||
|
ExistingSession: &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "already@populated.com",
|
||||||
|
Groups: nil,
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
EmailClaim: "email",
|
||||||
|
GroupsClaim: "groups",
|
||||||
|
ProfileJSON: map[string]interface{}{
|
||||||
|
"email": "new@thing.com",
|
||||||
|
"groups": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"groupId": "CIDAAS_USERS",
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
ExpectedError: nil,
|
||||||
|
ExpectedSession: &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "already@populated.com",
|
||||||
|
Groups: []string{"CIDAAS_USERS:USER", "cidaas:USER"},
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Empty Groups Claims": {
|
||||||
|
ExistingSession: &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "already@populated.com",
|
||||||
|
Groups: []string{},
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
EmailClaim: "email",
|
||||||
|
GroupsClaim: "groups",
|
||||||
|
ProfileJSON: map[string]interface{}{
|
||||||
|
"email": "new@thing.com",
|
||||||
|
"groups": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"groupId": "CIDAAS_USERS",
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
ExpectedError: nil,
|
||||||
|
ExpectedSession: &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "already@populated.com",
|
||||||
|
Groups: []string{"CIDAAS_USERS:USER", "cidaas:USER"},
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Missing Groups with Custom Claim": {
|
||||||
|
ExistingSession: &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "already@populated.com",
|
||||||
|
Groups: nil,
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
EmailClaim: "email",
|
||||||
|
GroupsClaim: "groups2",
|
||||||
|
ProfileJSON: map[string]interface{}{
|
||||||
|
"email": "already@populated.com",
|
||||||
|
"groups2": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"sub": "aa5181ea-0841-4ea7-b67f-81882f153d40",
|
||||||
|
"groupId": "CIDAAS_ADMINS",
|
||||||
|
"path": "/CIDAAS_ADMINS/",
|
||||||
|
"roles": []string{"ADMIN"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sub": "aa5181ea-0841-4ea7-b67f-81882f153d39",
|
||||||
|
"groupId": "customers",
|
||||||
|
"groupType": "Customers",
|
||||||
|
"path": "/customers/",
|
||||||
|
"roles": []string{
|
||||||
|
"CUSTOMER_ACCOUNT_LOGIN",
|
||||||
|
"GROUP_ADMIN",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupId": "CIDAAS_USERS",
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
ExpectedError: nil,
|
||||||
|
ExpectedSession: &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "already@populated.com",
|
||||||
|
Groups: []string{"CIDAAS_ADMINS:ADMIN", "customers:CUSTOMER_ACCOUNT_LOGIN", "customers:GROUP_ADMIN", "CIDAAS_USERS:USER", "cidaas:USER"},
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Just format Groups": {
|
||||||
|
ExistingSession: &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "already@populated.com",
|
||||||
|
Groups: nil,
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
EmailClaim: "email",
|
||||||
|
GroupsClaim: "groups2",
|
||||||
|
ProfileJSON: map[string]interface{}{
|
||||||
|
"email": "already@populated.com",
|
||||||
|
"groups2": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"sub": "aa5181ea-0841-4ea7-b67f-81882f153d39",
|
||||||
|
"groupId": "customers",
|
||||||
|
"groupType": "Customers",
|
||||||
|
"path": "/customers/",
|
||||||
|
"roles": []string{
|
||||||
|
"CUSTOMER_ACCOUNT_LOGIN",
|
||||||
|
"GROUP_ADMIN",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupId": "CIDAAS_USERS",
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
ExpectedError: nil,
|
||||||
|
ExpectedSession: &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "already@populated.com",
|
||||||
|
Groups: []string{"customers:CUSTOMER_ACCOUNT_LOGIN", "customers:GROUP_ADMIN", "CIDAAS_USERS:USER", "cidaas:USER"},
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Missing Groups String Profile URL Response": {
|
||||||
|
ExistingSession: &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "already@populated.com",
|
||||||
|
Groups: nil,
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
EmailClaim: "email",
|
||||||
|
GroupsClaim: "groups",
|
||||||
|
ProfileJSON: map[string]interface{}{
|
||||||
|
"groups": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"sub": "aa5181ea-0841-4ea7-b67f-81882f153d40",
|
||||||
|
"groupId": "CIDAAS_ADMINS",
|
||||||
|
"path": "/CIDAAS_ADMINS/",
|
||||||
|
"roles": []string{"ADMIN"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sub": "aa5181ea-0841-4ea7-b67f-81882f153d39",
|
||||||
|
"groupId": "customers",
|
||||||
|
"groupType": "Customers",
|
||||||
|
"path": "/customers/",
|
||||||
|
"roles": []string{
|
||||||
|
"CUSTOMER_ACCOUNT_LOGIN",
|
||||||
|
"GROUP_ADMIN",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupId": "CIDAAS_USERS",
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"roles": []string{"USER"},
|
||||||
|
},
|
||||||
|
ExpectedError: nil,
|
||||||
|
ExpectedSession: &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "already@populated.com",
|
||||||
|
Groups: []string{"CIDAAS_ADMINS:ADMIN", "customers:CUSTOMER_ACCOUNT_LOGIN", "customers:GROUP_ADMIN", "CIDAAS_USERS:USER", "cidaas:USER"},
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Missing Groups in both Claims and Profile URL": {
|
||||||
|
ExistingSession: &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "already@populated.com",
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
EmailClaim: "email",
|
||||||
|
GroupsClaim: "groups",
|
||||||
|
ProfileJSON: map[string]interface{}{
|
||||||
|
"email": "new@thing.com",
|
||||||
|
},
|
||||||
|
ExpectedError: nil,
|
||||||
|
ExpectedSession: &sessions.SessionState{
|
||||||
|
User: "already",
|
||||||
|
Email: "already@populated.com",
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for testName, tc := range testCases {
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
path := "/userinfo/"
|
||||||
|
jsonResp, err := json.Marshal(tc.ProfileJSON)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
server, provider := newTestCidaasSetup(map[string][]byte{path: jsonResp})
|
||||||
|
provider.ProfileURL, err = url.Parse(fmt.Sprintf("%s%s", server.URL, path))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
provider.EmailClaim = tc.EmailClaim
|
||||||
|
provider.GroupsClaim = tc.GroupsClaim
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err = provider.EnrichSession(context.Background(), tc.ExistingSession)
|
||||||
|
if tc.ExpectedError != nil {
|
||||||
|
assert.EqualError(t, err, tc.ExpectedError.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, *tc.ExpectedSession, *tc.ExistingSession)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCidaasProvider_RefreshSession(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
ExistingSession *sessions.SessionState
|
||||||
|
EmailClaim string
|
||||||
|
GroupsClaim string
|
||||||
|
ProfileJSON map[string]interface{}
|
||||||
|
RedeemJSON redeemTokenResponse
|
||||||
|
ExpectedRefreshed bool
|
||||||
|
ExpectedError error
|
||||||
|
ExpectedEmail string
|
||||||
|
ExpectedUser string
|
||||||
|
}{
|
||||||
|
"Refresh session successfully": {
|
||||||
|
ExistingSession: &sessions.SessionState{
|
||||||
|
User: "session.is.not.locked",
|
||||||
|
Email: "found@email.com",
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
RedeemJSON: redeemTokenResponse{
|
||||||
|
AccessToken: accessToken,
|
||||||
|
ExpiresIn: 10,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
ExpectedRefreshed: true,
|
||||||
|
ExpectedError: nil,
|
||||||
|
ExpectedEmail: defaultIDToken.Email,
|
||||||
|
ExpectedUser: defaultIDToken.Subject,
|
||||||
|
},
|
||||||
|
"Unable to refresh session": {
|
||||||
|
ExistingSession: &sessions.SessionState{
|
||||||
|
User: "session.is.unable.to.refresh",
|
||||||
|
Email: "found@email.com",
|
||||||
|
IDToken: idToken,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
},
|
||||||
|
ExpectedRefreshed: false,
|
||||||
|
ExpectedError: fmt.Errorf("unable to redeem refresh token: failed to get token: oauth2: server response missing access_token"),
|
||||||
|
ExpectedUser: "session.is.unable.to.refresh",
|
||||||
|
ExpectedEmail: "found@email.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for testName, tc := range testCases {
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
idToken, _ := newSignedTestIDToken(defaultIDToken)
|
||||||
|
tc.RedeemJSON.IDToken = idToken
|
||||||
|
redeemPath := "/token/"
|
||||||
|
redeemJSONResp, err := json.Marshal(tc.RedeemJSON)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
serverURL, server := newCidaasServer(
|
||||||
|
map[string][]byte{
|
||||||
|
redeemPath: redeemJSONResp,
|
||||||
|
})
|
||||||
|
provider := newCidaasProvider(serverURL)
|
||||||
|
|
||||||
|
// Disable session enrichment, because we want to focus on refreshing logic
|
||||||
|
provider.ProfileURL, err = url.Parse("")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
provider.RedeemURL, err = url.Parse(fmt.Sprintf("%s%s", server.URL, redeemPath))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
provider.GroupsClaim = tc.GroupsClaim
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
var refreshed bool
|
||||||
|
refreshed, err = provider.RefreshSession(context.Background(), tc.ExistingSession)
|
||||||
|
|
||||||
|
if tc.ExpectedError != nil {
|
||||||
|
assert.EqualError(t, err, tc.ExpectedError.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tc.ExpectedRefreshed, refreshed)
|
||||||
|
assert.Equal(t, tc.ExpectedEmail, tc.ExistingSession.Email)
|
||||||
|
assert.Equal(t, tc.ExpectedUser, tc.ExistingSession.User)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,6 +45,8 @@ func NewProvider(providerConfig options.Provider) (Provider, error) {
|
||||||
return NewMicrosoftEntraIDProvider(providerData, providerConfig), nil
|
return NewMicrosoftEntraIDProvider(providerData, providerConfig), nil
|
||||||
case options.BitbucketProvider:
|
case options.BitbucketProvider:
|
||||||
return NewBitbucketProvider(providerData, providerConfig.BitbucketConfig), nil
|
return NewBitbucketProvider(providerData, providerConfig.BitbucketConfig), nil
|
||||||
|
case options.CidaasProvider:
|
||||||
|
return NewCIDAASProvider(providerData, providerConfig), nil
|
||||||
case options.DigitalOceanProvider:
|
case options.DigitalOceanProvider:
|
||||||
return NewDigitalOceanProvider(providerData), nil
|
return NewDigitalOceanProvider(providerData), nil
|
||||||
case options.FacebookProvider:
|
case options.FacebookProvider:
|
||||||
|
|
@ -188,7 +190,8 @@ func providerRequiresOIDCProviderVerifier(providerType options.ProviderType) (bo
|
||||||
options.GoogleProvider, options.KeycloakProvider, options.LinkedInProvider, options.LoginGovProvider,
|
options.GoogleProvider, options.KeycloakProvider, options.LinkedInProvider, options.LoginGovProvider,
|
||||||
options.NextCloudProvider, options.SourceHutProvider:
|
options.NextCloudProvider, options.SourceHutProvider:
|
||||||
return false, nil
|
return false, nil
|
||||||
case options.ADFSProvider, options.AzureProvider, options.GitLabProvider, options.KeycloakOIDCProvider, options.OIDCProvider, options.MicrosoftEntraIDProvider:
|
case options.OIDCProvider, options.ADFSProvider, options.AzureProvider, options.CidaasProvider,
|
||||||
|
options.GitLabProvider, options.KeycloakOIDCProvider, options.MicrosoftEntraIDProvider:
|
||||||
return true, nil
|
return true, nil
|
||||||
default:
|
default:
|
||||||
return false, fmt.Errorf("unknown provider type: %s", providerType)
|
return false, fmt.Errorf("unknown provider type: %s", providerType)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue