mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-02-15 00:31:30 -05:00
229 lines
6.7 KiB
Go
229 lines
6.7 KiB
Go
/*
|
|
* Copyright 2017-2019 Kopano and its licensors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
package managers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/gorilla/mux"
|
|
"github.com/libregraph/oidc-go"
|
|
"github.com/longsleep/rndm"
|
|
|
|
konnect "github.com/libregraph/lico"
|
|
"github.com/libregraph/lico/identity"
|
|
|
|
"github.com/libregraph/lico/oidc/payload"
|
|
)
|
|
|
|
const dummyIdentityManagerName = "dummy"
|
|
|
|
// DummyIdentityManager implements an identity manager which always grants
|
|
// access to a fixed user id.
|
|
type DummyIdentityManager struct {
|
|
sub string
|
|
|
|
scopesSupported []string
|
|
}
|
|
|
|
// NewDummyIdentityManager creates a new DummyIdentityManager from the
|
|
// provided parameters.
|
|
func NewDummyIdentityManager(c *identity.Config, sub string) *DummyIdentityManager {
|
|
im := &DummyIdentityManager{
|
|
sub: sub,
|
|
|
|
scopesSupported: setupSupportedScopes([]string{
|
|
oidc.ScopeProfile,
|
|
oidc.ScopeEmail,
|
|
}, nil, c.ScopesSupported),
|
|
}
|
|
|
|
return im
|
|
}
|
|
|
|
type dummyUser struct {
|
|
raw string
|
|
}
|
|
|
|
func (u *dummyUser) Raw() string {
|
|
return u.raw
|
|
}
|
|
|
|
func (u *dummyUser) Subject() string {
|
|
sub, _ := getPublicSubject([]byte(u.raw), []byte(dummyIdentityManagerName))
|
|
return sub
|
|
}
|
|
|
|
func (u *dummyUser) Email() string {
|
|
return fmt.Sprintf("%s@%s.local", u.raw, u.raw)
|
|
}
|
|
|
|
func (u *dummyUser) EmailVerified() bool {
|
|
return false
|
|
}
|
|
|
|
func (u *dummyUser) Name() string {
|
|
return fmt.Sprintf("Foo %s", strings.Title(u.raw))
|
|
}
|
|
|
|
func (u *dummyUser) Claims() jwt.MapClaims {
|
|
claims := make(jwt.MapClaims)
|
|
claims[konnect.IdentifiedUserIDClaim] = u.raw
|
|
|
|
return claims
|
|
}
|
|
|
|
// Authenticate implements the identity.Manager interface.
|
|
func (im *DummyIdentityManager) Authenticate(ctx context.Context, rw http.ResponseWriter, req *http.Request, ar *payload.AuthenticationRequest, next identity.Manager) (identity.AuthRecord, error) {
|
|
user := &dummyUser{im.sub}
|
|
|
|
// Check request.
|
|
err := ar.Verify(user.Subject())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
auth := identity.NewAuthRecord(im, user.Subject(), nil, nil, nil)
|
|
auth.SetUser(user)
|
|
|
|
return auth, nil
|
|
}
|
|
|
|
// Authorize implements the identity.Manager interface.
|
|
func (im *DummyIdentityManager) Authorize(ctx context.Context, rw http.ResponseWriter, req *http.Request, ar *payload.AuthenticationRequest, auth identity.AuthRecord) (identity.AuthRecord, error) {
|
|
promptConsent := false
|
|
var approvedScopes map[string]bool
|
|
|
|
// Check prompt value.
|
|
switch {
|
|
case ar.Prompts[oidc.PromptConsent] == true:
|
|
promptConsent = true
|
|
default:
|
|
// Let all other prompt values pass.
|
|
}
|
|
|
|
// TODO(longsleep): Move the code below to general function.
|
|
// TODO(longsleep): Validate scopes and force prompt.
|
|
approvedScopes = ar.Scopes
|
|
|
|
// Offline access validation.
|
|
// http://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
|
if ok, _ := ar.Scopes[oidc.ScopeOfflineAccess]; ok {
|
|
if !promptConsent {
|
|
// Ensure that the prompt parameter contains consent unless
|
|
// other conditions for processing the request permitting offline
|
|
// access to the requested resources are in place; unless one or
|
|
// both of these conditions are fulfilled, then it MUST ignore the
|
|
// offline_access request,
|
|
delete(ar.Scopes, oidc.ScopeOfflineAccess)
|
|
}
|
|
}
|
|
|
|
if promptConsent {
|
|
if ar.Prompts[oidc.PromptNone] == true {
|
|
return auth, ar.NewError(oidc.ErrorCodeOIDCInteractionRequired, "consent required")
|
|
}
|
|
|
|
// TODO(longsleep): Implement consent page.
|
|
return auth, ar.NewError(oidc.ErrorCodeOIDCInteractionRequired, "consent required, but page not implemented")
|
|
}
|
|
|
|
auth.AuthorizeScopes(approvedScopes)
|
|
auth.AuthorizeClaims(ar.Claims)
|
|
return auth, nil
|
|
}
|
|
|
|
// EndSession implements the identity.Manager interface.
|
|
func (im *DummyIdentityManager) EndSession(ctx context.Context, rw http.ResponseWriter, req *http.Request, esr *payload.EndSessionRequest) error {
|
|
user := &dummyUser{im.sub}
|
|
|
|
err := esr.Verify(user.Subject())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ApproveScopes implements the Backend interface.
|
|
func (im *DummyIdentityManager) ApproveScopes(ctx context.Context, sub string, audience string, approvedScopes map[string]bool) (string, error) {
|
|
ref := rndm.GenerateRandomString(32)
|
|
|
|
// TODO(longsleep): Store generated ref with provided data.
|
|
return ref, nil
|
|
}
|
|
|
|
// ApprovedScopes implements the Backend interface.
|
|
func (im *DummyIdentityManager) ApprovedScopes(ctx context.Context, sub string, audience string, ref string) (map[string]bool, error) {
|
|
if ref == "" {
|
|
return nil, fmt.Errorf("SimplePasswdBackend: invalid ref")
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// Fetch implements the identity.Manager interface.
|
|
func (im *DummyIdentityManager) Fetch(ctx context.Context, userID string, sessionRef *string, scopes map[string]bool, requestedClaimsMaps []*payload.ClaimsRequestMap, requestedScopes map[string]bool) (identity.AuthRecord, bool, error) {
|
|
if userID != im.sub {
|
|
return nil, false, fmt.Errorf("DummyIdentityManager: no user")
|
|
}
|
|
|
|
user := &dummyUser{im.sub}
|
|
|
|
authorizedScopes, _ := identity.AuthorizeScopes(im, user, scopes)
|
|
claims := identity.GetUserClaimsForScopes(user, authorizedScopes, requestedClaimsMaps)
|
|
|
|
return identity.NewAuthRecord(im, user.Subject(), authorizedScopes, nil, claims), true, nil
|
|
}
|
|
|
|
// Name implements the identity.Manager interface.
|
|
func (im *DummyIdentityManager) Name() string {
|
|
return dummyIdentityManagerName
|
|
}
|
|
|
|
// ScopesSupported implements the identity.Manager interface.
|
|
func (im *DummyIdentityManager) ScopesSupported(scopes map[string]bool) []string {
|
|
return im.scopesSupported
|
|
}
|
|
|
|
// ClaimsSupported implements the identity.Manager interface.
|
|
func (im *DummyIdentityManager) ClaimsSupported(claims []string) []string {
|
|
return []string{
|
|
oidc.NameClaim,
|
|
oidc.EmailClaim,
|
|
oidc.EmailVerifiedClaim,
|
|
}
|
|
}
|
|
|
|
// AddRoutes implements the identity.Manager interface.
|
|
func (im *DummyIdentityManager) AddRoutes(ctx context.Context, router *mux.Router) {
|
|
}
|
|
|
|
// OnSetLogon implements the identity.Manager interface.
|
|
func (im *DummyIdentityManager) OnSetLogon(func(ctx context.Context, rw http.ResponseWriter, user identity.User) error) error {
|
|
return nil
|
|
}
|
|
|
|
// OnUnsetLogon implements the identity.Manager interface.
|
|
func (im *DummyIdentityManager) OnUnsetLogon(func(ctx context.Context, rw http.ResponseWriter) error) error {
|
|
return nil
|
|
}
|