feat: Identity brokering (#730)

* feat: add/ remove external idps

* feat: external idp add /remove

* fix: auth proto

* fix: handle login

* feat: loginpolicy on authrequest

* feat: idp providers on login

* feat: link external idp

* fix: check login policy on check username

* feat: add mapping fields for idp config

* feat: use user org id if existing

* feat: use user org id if existing

* feat: register external user

* feat: register external user

* feat: user linking

* feat: user linking

* feat: design external login

* feat: design external login

* fix: tests

* fix: regenerate login design

* feat: next step test linking process

* feat: next step test linking process

* feat: cascade remove external idps on user

* fix: tests

* fix: tests

* feat: external idp requsts on users

* fix: generate protos

* feat: login styles

* feat: login styles

* fix: link user

* fix: register user on specifig org

* fix: user linking

* fix: register external, linking auto

* fix: remove unnecessary request from proto

* fix: tests

* fix: new oidc package

* fix: migration version

* fix: policy permissions

* Update internal/ui/login/static/i18n/en.yaml

Co-authored-by: Livio Amstutz <[email protected]>

* Update internal/ui/login/static/i18n/en.yaml

Co-authored-by: Livio Amstutz <[email protected]>

* Update internal/ui/login/handler/renderer.go

Co-authored-by: Livio Amstutz <[email protected]>

* Update internal/ui/login/handler/renderer.go

Co-authored-by: Livio Amstutz <[email protected]>

* fix: pr requests

* Update internal/ui/login/handler/link_users_handler.go

Co-authored-by: Livio Amstutz <[email protected]>

* fix: pr requests

* fix: pr requests

* fix: pr requests

* fix: login name size

* fix: profile image light

* fix: colors

* fix: pr requests

* fix: remove redirect uri validator

* fix: remove redirect uri validator

Co-authored-by: Livio Amstutz <[email protected]>
This commit is contained in:
Fabi
2020-09-18 13:26:28 +02:00
committed by GitHub
co-authored by Livio Amstutz
parent 1d542a0c57
commit 320ddfa46d
141 changed files with 30054 additions and 12532 deletions
@@ -28,3 +28,7 @@ func (l *Login) getAuthRequestAndParseData(r *http.Request, data interface{}) (*
err = l.parser.Parse(r, data)
return authReq, err
}
func (l *Login) getParseData(r *http.Request, data interface{}) error {
return l.parser.Parse(r, data)
}
@@ -0,0 +1,269 @@
package handler
import (
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/rp"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/crypto"
caos_errors "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
iam_model "github.com/caos/zitadel/internal/iam/model"
org_model "github.com/caos/zitadel/internal/org/model"
usr_model "github.com/caos/zitadel/internal/user/model"
"net/http"
"strings"
"time"
)
const (
queryIDPConfigID = "idpConfigID"
tmplExternalNotFoundOption = "externalnotfoundoption"
)
type externalIDPData struct {
IDPConfigID string `schema:"idpConfigID"`
}
type externalIDPCallbackData struct {
State string `schema:"state"`
Code string `schema:"code"`
}
type externalNotFoundOptionFormData struct {
Link bool `schema:"link"`
AutoRegister bool `schema:"autoregister"`
}
type externalNotFoundOptionData struct {
baseData
}
func (l *Login) handleExternalLogin(w http.ResponseWriter, r *http.Request) {
data := new(externalIDPData)
authReq, err := l.getAuthRequestAndParseData(r, data)
if err != nil {
l.renderError(w, r, authReq, err)
return
}
if authReq == nil {
http.Redirect(w, r, l.zitadelURL, http.StatusFound)
return
}
idpConfig, err := l.getIDPConfigByID(r, data.IDPConfigID)
if err != nil {
l.renderError(w, r, authReq, err)
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
err = l.authRepo.SelectExternalIDP(r.Context(), authReq.ID, idpConfig.IDPConfigID, userAgentID)
if err != nil {
l.renderLogin(w, r, authReq, err)
return
}
if !idpConfig.IsOIDC {
l.renderError(w, r, authReq, caos_errors.ThrowInternal(nil, "LOGIN-Rio9s", "Errors.User.ExternalIDP.IDPTypeNotImplemented"))
return
}
l.handleOIDCAuthorize(w, r, authReq, idpConfig, EndpointExternalLoginCallback)
}
func (l *Login) handleOIDCAuthorize(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) {
provider := l.getRPConfig(w, r, authReq, idpConfig, callbackEndpoint)
http.Redirect(w, r, rp.AuthURL(authReq.ID, provider), http.StatusFound)
}
func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Request) {
data := new(externalIDPCallbackData)
err := l.getParseData(r, data)
if err != nil {
l.renderError(w, r, nil, err)
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID)
if err != nil {
l.renderError(w, r, authReq, err)
return
}
idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
if err != nil {
l.renderError(w, r, authReq, err)
return
}
provider := l.getRPConfig(w, r, authReq, idpConfig, EndpointExternalLoginCallback)
tokens, err := rp.CodeExchange(r.Context(), data.Code, provider)
if err != nil {
l.renderLogin(w, r, authReq, err)
return
}
l.handleExternalUserAuthenticated(w, r, authReq, idpConfig, userAgentID, tokens)
}
func (l *Login) getRPConfig(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) rp.RelayingParty {
oidcClientSecret, err := crypto.DecryptString(idpConfig.OIDCClientSecret, l.IDPConfigAesCrypto)
if err != nil {
l.renderError(w, r, authReq, err)
return nil
}
provider, err := rp.NewRelayingPartyOIDC(idpConfig.OIDCIssuer, idpConfig.OIDCClientID, oidcClientSecret, l.baseURL+callbackEndpoint, idpConfig.OIDCScopes, rp.WithVerifierOpts(rp.WithIssuedAtOffset(3*time.Second)))
if err != nil {
l.renderError(w, r, authReq, err)
return nil
}
return provider
}
func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) {
externalUser := l.mapTokenToLoginUser(tokens, idpConfig)
err := l.authRepo.CheckExternalUserLogin(r.Context(), authReq.ID, userAgentID, externalUser)
if err != nil {
l.renderExternalNotFoundOption(w, r, authReq, nil)
return
}
l.renderNextStep(w, r, authReq)
}
func (l *Login) renderExternalNotFoundOption(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
var errType, errMessage string
if err != nil {
errMessage = l.getErrorMessage(r, err)
}
data := externalNotFoundOptionData{
baseData: l.getBaseData(r, authReq, "ExternalNotFoundOption", errType, errMessage),
}
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplExternalNotFoundOption], data, nil)
}
func (l *Login) handleExternalNotFoundOptionCheck(w http.ResponseWriter, r *http.Request) {
data := new(externalNotFoundOptionFormData)
authReq, err := l.getAuthRequestAndParseData(r, data)
if err != nil {
l.renderError(w, r, authReq, err)
return
}
if data.Link {
l.renderLogin(w, r, authReq, nil)
return
}
l.handleAutoRegister(w, r, authReq)
}
func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) {
orgIamPolicy, err := l.getOrgIamPolicy(r, authReq.GetScopeOrgID())
if err != nil {
l.renderExternalNotFoundOption(w, r, authReq, err)
return
}
iam, err := l.authRepo.GetIAM(r.Context())
if err != nil {
l.renderExternalNotFoundOption(w, r, authReq, err)
return
}
resourceOwner := iam.GlobalOrgID
member := &org_model.OrgMember{
ObjectRoot: models.ObjectRoot{AggregateID: iam.GlobalOrgID},
Roles: []string{orgProjectCreatorRole},
}
if authReq.GetScopeOrgID() != iam.GlobalOrgID && authReq.GetScopeOrgID() != "" {
member = nil
resourceOwner = authReq.GetScopeOrgID()
}
idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
if err != nil {
l.renderExternalNotFoundOption(w, r, authReq, err)
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
user, externalIDP := l.mapExternalUserToLoginUser(orgIamPolicy, authReq.LinkingUsers[len(authReq.LinkingUsers)-1], idpConfig)
err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, member, authReq.ID, userAgentID, resourceOwner)
if err != nil {
l.renderExternalNotFoundOption(w, r, authReq, err)
return
}
l.renderNextStep(w, r, authReq)
}
func (l *Login) mapTokenToLoginUser(tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) *model.ExternalUser {
displayName := tokens.IDTokenClaims.PreferredUsername
switch idpConfig.OIDCIDPDisplayNameMapping {
case iam_model.OIDCMappingFieldEmail:
if tokens.IDTokenClaims.EmailVerified && tokens.IDTokenClaims.Email != "" {
displayName = tokens.IDTokenClaims.Email
}
}
externalUser := &model.ExternalUser{
IDPConfigID: idpConfig.IDPConfigID,
ExternalUserID: tokens.IDTokenClaims.Subject,
PreferredUsername: tokens.IDTokenClaims.PreferredUsername,
DisplayName: displayName,
FirstName: tokens.IDTokenClaims.GivenName,
LastName: tokens.IDTokenClaims.FamilyName,
NickName: tokens.IDTokenClaims.Nickname,
Email: tokens.IDTokenClaims.Email,
IsEmailVerified: tokens.IDTokenClaims.EmailVerified,
}
if tokens.IDTokenClaims.PhoneNumber != "" {
externalUser.Phone = tokens.IDTokenClaims.PhoneNumber
externalUser.IsPhoneVerified = tokens.IDTokenClaims.PhoneNumberVerified
}
return externalUser
}
func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *org_model.OrgIAMPolicy, linkingUser *model.ExternalUser, idpConfig *iam_model.IDPConfigView) (*usr_model.User, *usr_model.ExternalIDP) {
username := linkingUser.PreferredUsername
switch idpConfig.OIDCUsernameMapping {
case iam_model.OIDCMappingFieldEmail:
if linkingUser.IsEmailVerified && linkingUser.Email != "" {
username = linkingUser.Email
}
}
if orgIamPolicy.UserLoginMustBeDomain {
splittedUsername := strings.Split(username, "@")
if len(splittedUsername) > 1 {
username = splittedUsername[0]
}
}
user := &usr_model.User{
UserName: username,
Human: &usr_model.Human{
Profile: &usr_model.Profile{
FirstName: linkingUser.FirstName,
LastName: linkingUser.LastName,
PreferredLanguage: linkingUser.PreferredLanguage,
NickName: linkingUser.NickName,
},
Email: &usr_model.Email{
EmailAddress: linkingUser.Email,
IsEmailVerified: linkingUser.IsEmailVerified,
},
},
}
if linkingUser.Phone != "" {
user.Phone = &usr_model.Phone{
PhoneNumber: linkingUser.Phone,
IsPhoneVerified: linkingUser.IsPhoneVerified,
}
}
displayName := linkingUser.PreferredUsername
switch idpConfig.OIDCIDPDisplayNameMapping {
case iam_model.OIDCMappingFieldEmail:
if linkingUser.IsEmailVerified && linkingUser.Email != "" {
displayName = linkingUser.Email
}
}
externalIDP := &usr_model.ExternalIDP{
IDPConfigID: idpConfig.IDPConfigID,
UserID: linkingUser.ExternalUserID,
DisplayName: displayName,
}
return user, externalIDP
}
@@ -0,0 +1,155 @@
package handler
import (
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/rp"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/auth_request/model"
caos_errors "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
iam_model "github.com/caos/zitadel/internal/iam/model"
org_model "github.com/caos/zitadel/internal/org/model"
usr_model "github.com/caos/zitadel/internal/user/model"
"net/http"
"strings"
)
func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) {
data := new(externalIDPData)
authReq, err := l.getAuthRequestAndParseData(r, data)
if err != nil {
l.renderError(w, r, authReq, err)
return
}
if authReq == nil {
http.Redirect(w, r, l.zitadelURL, http.StatusFound)
return
}
idpConfig, err := l.getIDPConfigByID(r, data.IDPConfigID)
if err != nil {
l.renderError(w, r, authReq, err)
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
err = l.authRepo.SelectExternalIDP(r.Context(), authReq.ID, idpConfig.IDPConfigID, userAgentID)
if err != nil {
l.renderLogin(w, r, authReq, err)
return
}
if !idpConfig.IsOIDC {
l.renderError(w, r, authReq, caos_errors.ThrowInternal(nil, "LOGIN-Rio9s", "Errors.User.ExternalIDP.IDPTypeNotImplemented"))
return
}
l.handleOIDCAuthorize(w, r, authReq, idpConfig, EndpointExternalRegisterCallback)
}
func (l *Login) handleExternalRegisterCallback(w http.ResponseWriter, r *http.Request) {
data := new(externalIDPCallbackData)
err := l.getParseData(r, data)
if err != nil {
l.renderError(w, r, nil, err)
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID)
if err != nil {
l.renderError(w, r, authReq, err)
return
}
idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
if err != nil {
l.renderError(w, r, authReq, err)
return
}
provider := l.getRPConfig(w, r, authReq, idpConfig, EndpointExternalRegisterCallback)
tokens, err := rp.CodeExchange(r.Context(), data.Code, provider)
if err != nil {
l.renderRegisterOption(w, r, authReq, err)
return
}
l.handleExternalUserRegister(w, r, authReq, idpConfig, userAgentID, tokens)
}
func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) {
orgIamPolicy, err := l.getOrgIamPolicy(r, authReq.GetScopeOrgID())
if err != nil {
l.renderRegisterOption(w, r, authReq, err)
return
}
iam, err := l.authRepo.GetIAM(r.Context())
if err != nil {
l.renderRegisterOption(w, r, authReq, err)
return
}
resourceOwner := iam.GlobalOrgID
member := &org_model.OrgMember{
ObjectRoot: models.ObjectRoot{AggregateID: iam.GlobalOrgID},
Roles: []string{orgProjectCreatorRole},
}
if authReq.GetScopeOrgID() != iam.GlobalOrgID && authReq.GetScopeOrgID() != "" {
member = nil
resourceOwner = authReq.GetScopeOrgID()
}
user, externalIDP := l.mapTokenToLoginUserAndExternalIDP(orgIamPolicy, tokens, idpConfig)
_, err = l.authRepo.RegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, member, resourceOwner)
if err != nil {
l.renderRegisterOption(w, r, authReq, err)
return
}
l.renderNextStep(w, r, authReq)
}
func (l *Login) mapTokenToLoginUserAndExternalIDP(orgIamPolicy *org_model.OrgIAMPolicy, tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) (*usr_model.User, *usr_model.ExternalIDP) {
username := tokens.IDTokenClaims.PreferredUsername
switch idpConfig.OIDCUsernameMapping {
case iam_model.OIDCMappingFieldEmail:
if tokens.IDTokenClaims.EmailVerified && tokens.IDTokenClaims.Email != "" {
username = tokens.IDTokenClaims.Email
}
}
if orgIamPolicy.UserLoginMustBeDomain {
splittedUsername := strings.Split(username, "@")
if len(splittedUsername) > 1 {
username = splittedUsername[0]
}
}
user := &usr_model.User{
UserName: username,
Human: &usr_model.Human{
Profile: &usr_model.Profile{
FirstName: tokens.IDTokenClaims.GivenName,
LastName: tokens.IDTokenClaims.FamilyName,
PreferredLanguage: tokens.IDTokenClaims.Locale,
NickName: tokens.IDTokenClaims.Nickname,
},
Email: &usr_model.Email{
EmailAddress: tokens.IDTokenClaims.Email,
IsEmailVerified: tokens.IDTokenClaims.EmailVerified,
},
},
}
if tokens.IDTokenClaims.PhoneNumber != "" {
user.Phone = &usr_model.Phone{
PhoneNumber: tokens.IDTokenClaims.PhoneNumber,
IsPhoneVerified: tokens.IDTokenClaims.PhoneNumberVerified,
}
}
displayName := tokens.IDTokenClaims.PreferredUsername
switch idpConfig.OIDCIDPDisplayNameMapping {
case iam_model.OIDCMappingFieldEmail:
if tokens.IDTokenClaims.EmailVerified && tokens.IDTokenClaims.Email != "" {
displayName = tokens.IDTokenClaims.Email
}
}
externalIDP := &usr_model.ExternalIDP{
IDPConfigID: idpConfig.IDPConfigID,
UserID: tokens.IDTokenClaims.Subject,
DisplayName: displayName,
}
return user, externalIDP
}
@@ -0,0 +1,24 @@
package handler
import (
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"net/http"
"github.com/caos/zitadel/internal/auth_request/model"
)
const (
tmplLinkUsersDone = "linkusersdone"
)
func (l *Login) linkUsers(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
err = l.authRepo.LinkExternalUsers(setContext(r.Context(), authReq.UserOrgID), authReq.ID, userAgentID)
l.renderLinkUsersDone(w, r, authReq, err)
}
func (l *Login) renderLinkUsersDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
var errType, errMessage string
data := l.getUserData(r, authReq, "Linking Users Done", errType, errMessage)
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplLinkUsersDone], data, nil)
}
+11 -1
View File
@@ -2,6 +2,7 @@ package handler
import (
"context"
"github.com/caos/zitadel/internal/config/systemdefaults"
"net"
"net/http"
@@ -28,11 +29,14 @@ type Login struct {
renderer *Renderer
parser *form.Parser
authRepo auth_repository.Repository
baseURL string
zitadelURL string
oidcAuthCallbackURL string
IDPConfigAesCrypto crypto.EncryptionAlgorithm
}
type Config struct {
BaseURL string
OidcAuthCallbackURL string
ZitadelURL string
LanguageCookieName string
@@ -53,11 +57,17 @@ const (
handlerPrefix = "/login"
)
func CreateLogin(config Config, authRepo *eventsourcing.EsRepository, localDevMode bool) (*Login, string) {
func CreateLogin(config Config, authRepo *eventsourcing.EsRepository, systemDefaults systemdefaults.SystemDefaults, localDevMode bool) (*Login, string) {
aesCrypto, err := crypto.NewAESCrypto(systemDefaults.IDPConfigVerificationKey)
if err != nil {
logging.Log("HANDL-s90ew").WithError(err).Debug("error create new aes crypto")
}
login := &Login{
oidcAuthCallbackURL: config.OidcAuthCallbackURL,
baseURL: config.BaseURL,
zitadelURL: config.ZitadelURL,
authRepo: authRepo,
IDPConfigAesCrypto: aesCrypto,
}
prefix := ""
if localDevMode {
+7 -3
View File
@@ -30,12 +30,12 @@ func (l *Login) handleLogin(w http.ResponseWriter, r *http.Request) {
}
func (l *Login) handleLoginName(w http.ResponseWriter, r *http.Request) {
authSession, err := l.getAuthRequest(r)
authReq, err := l.getAuthRequest(r)
if err != nil {
l.renderError(w, r, authSession, err)
l.renderError(w, r, authReq, err)
return
}
l.renderLogin(w, r, authSession, nil)
l.renderLogin(w, r, authReq, nil)
}
func (l *Login) handleLoginNameCheck(w http.ResponseWriter, r *http.Request) {
@@ -46,6 +46,10 @@ func (l *Login) handleLoginNameCheck(w http.ResponseWriter, r *http.Request) {
return
}
if data.Register {
if authReq.LoginPolicy != nil && authReq.LoginPolicy.AllowExternalIDP && authReq.AllowedExternalIDPs != nil && len(authReq.AllowedExternalIDPs) > 0 {
l.handleRegisterOption(w, r)
return
}
l.handleRegister(w, r)
return
}
@@ -1,10 +0,0 @@
package handler
import (
org_model "github.com/caos/zitadel/internal/org/model"
"net/http"
)
func (l *Login) getOrgIamPolicy(r *http.Request, orgID string) (*org_model.OrgIAMPolicy, error) {
return l.authRepo.GetOrgIamPolicy(r.Context(), orgID)
}
@@ -0,0 +1,18 @@
package handler
import (
iam_model "github.com/caos/zitadel/internal/iam/model"
org_model "github.com/caos/zitadel/internal/org/model"
"net/http"
)
func (l *Login) getOrgIamPolicy(r *http.Request, orgID string) (*org_model.OrgIAMPolicy, error) {
if orgID == "" {
return l.authRepo.GetDefaultOrgIamPolicy(r.Context()), nil
}
return l.authRepo.GetOrgIamPolicy(r.Context(), orgID)
}
func (l *Login) getIDPConfigByID(r *http.Request, idpConfigID string) (*iam_model.IDPConfigView, error) {
return l.authRepo.GetIDPConfigByID(r.Context(), idpConfigID)
}
@@ -66,11 +66,16 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
return
}
resourceOwner := iam.GlobalOrgID
member := &org_model.OrgMember{
ObjectRoot: models.ObjectRoot{AggregateID: iam.GlobalOrgID},
Roles: []string{orgProjectCreatorRole},
}
user, err := l.authRepo.Register(setContext(r.Context(), iam.GlobalOrgID), data.toUserModel(), member, iam.GlobalOrgID)
if authRequest.GetScopeOrgID() != "" && authRequest.GetScopeOrgID() != iam.GlobalOrgID {
member = nil
resourceOwner = authRequest.GetScopeOrgID()
}
user, err := l.authRepo.Register(setContext(r.Context(), resourceOwner), data.toUserModel(), member, resourceOwner)
if err != nil {
l.renderRegister(w, r, authRequest, data, err)
return
@@ -0,0 +1,53 @@
package handler
import (
"github.com/caos/zitadel/internal/auth_request/model"
"net/http"
)
const (
tmplRegisterOption = "registeroption"
)
type registerOptionFormData struct {
UsernamePassword bool `schema:"usernamepassword"`
}
type registerOptionData struct {
baseData
}
func (l *Login) handleRegisterOption(w http.ResponseWriter, r *http.Request) {
data := new(registerOptionFormData)
authRequest, err := l.getAuthRequestAndParseData(r, data)
if err != nil {
l.renderError(w, r, authRequest, err)
return
}
l.renderRegisterOption(w, r, authRequest, nil)
}
func (l *Login) renderRegisterOption(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
var errType, errMessage string
if err != nil {
errMessage = l.getErrorMessage(r, err)
}
data := registerOptionData{
baseData: l.getBaseData(r, authReq, "RegisterOption", errType, errMessage),
}
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplRegisterOption], data, nil)
}
func (l *Login) handleRegisterOptionCheck(w http.ResponseWriter, r *http.Request) {
data := new(registerOptionFormData)
authReq, err := l.getAuthRequestAndParseData(r, data)
if err != nil {
l.renderError(w, r, authReq, err)
return
}
if data.UsernamePassword {
l.handleRegister(w, r)
return
}
l.handleRegisterOption(w, r)
}
+79 -35
View File
@@ -3,14 +3,14 @@ package handler
import (
"errors"
"fmt"
"github.com/caos/logging"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/gorilla/csrf"
"golang.org/x/text/language"
"html/template"
"net/http"
"path"
"github.com/caos/logging"
"github.com/gorilla/csrf"
"golang.org/x/text/language"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/auth_request/model"
caos_errs "github.com/caos/zitadel/internal/errors"
@@ -32,28 +32,31 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str
pathPrefix: pathPrefix,
}
tmplMapping := map[string]string{
tmplError: "error.html",
tmplLogin: "login.html",
tmplUserSelection: "select_user.html",
tmplPassword: "password.html",
tmplMfaVerify: "mfa_verify.html",
tmplMfaPrompt: "mfa_prompt.html",
tmplMfaInitVerify: "mfa_init_verify.html",
tmplMfaInitDone: "mfa_init_done.html",
tmplMailVerification: "mail_verification.html",
tmplMailVerified: "mail_verified.html",
tmplInitPassword: "init_password.html",
tmplInitPasswordDone: "init_password_done.html",
tmplInitUser: "init_user.html",
tmplInitUserDone: "init_user_done.html",
tmplPasswordResetDone: "password_reset_done.html",
tmplChangePassword: "change_password.html",
tmplChangePasswordDone: "change_password_done.html",
tmplRegister: "register.html",
tmplLogoutDone: "logout_done.html",
tmplRegisterOrg: "register_org.html",
tmplChangeUsername: "change_username.html",
tmplChangeUsernameDone: "change_username_done.html",
tmplError: "error.html",
tmplLogin: "login.html",
tmplUserSelection: "select_user.html",
tmplPassword: "password.html",
tmplMfaVerify: "mfa_verify.html",
tmplMfaPrompt: "mfa_prompt.html",
tmplMfaInitVerify: "mfa_init_verify.html",
tmplMfaInitDone: "mfa_init_done.html",
tmplMailVerification: "mail_verification.html",
tmplMailVerified: "mail_verified.html",
tmplInitPassword: "init_password.html",
tmplInitPasswordDone: "init_password_done.html",
tmplInitUser: "init_user.html",
tmplInitUserDone: "init_user_done.html",
tmplPasswordResetDone: "password_reset_done.html",
tmplChangePassword: "change_password.html",
tmplChangePasswordDone: "change_password_done.html",
tmplRegisterOption: "register_option.html",
tmplRegister: "register.html",
tmplLogoutDone: "logout_done.html",
tmplRegisterOrg: "register_org.html",
tmplChangeUsername: "change_username.html",
tmplChangeUsernameDone: "change_username_done.html",
tmplLinkUsersDone: "link_users_done.html",
tmplExternalNotFoundOption: "external_not_found_option.html",
}
funcs := map[string]interface{}{
"resourceUrl": func(file string) string {
@@ -65,6 +68,12 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str
"loginUrl": func() string {
return path.Join(r.pathPrefix, EndpointLogin)
},
"externalIDPAuthURL": func(authReqID, idpConfigID string) string {
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s&%s=%s", EndpointExternalLogin, queryAuthRequestID, authReqID, queryIDPConfigID, idpConfigID))
},
"externalIDPRegisterURL": func(authReqID, idpConfigID string) string {
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s&%s=%s", EndpointExternalRegister, queryAuthRequestID, authReqID, queryIDPConfigID, idpConfigID))
},
"registerUrl": func(id string) string {
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s", EndpointRegister, queryAuthRequestID, id))
},
@@ -107,6 +116,9 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str
"changePasswordUrl": func() string {
return path.Join(r.pathPrefix, EndpointChangePassword)
},
"registerOptionUrl": func() string {
return path.Join(r.pathPrefix, EndpointRegisterOption)
},
"registrationUrl": func() string {
return path.Join(r.pathPrefix, EndpointRegister)
},
@@ -116,6 +128,9 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str
"changeUsernameUrl": func() string {
return path.Join(r.pathPrefix, EndpointChangeUsername)
},
"externalNotFoundOptionUrl": func() string {
return path.Join(r.pathPrefix, EndpointExternalNotFoundOption)
},
"selectedLanguage": func(l string) bool {
return false
},
@@ -186,6 +201,10 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *
l.renderInitUser(w, r, authReq, "", "", step.PasswordSet, nil)
case *model.ChangeUsernameStep:
l.renderChangeUsername(w, r, authReq, nil)
case *model.LinkUsersStep:
l.linkUsers(w, r, authReq, err)
case *model.ExternalNotFoundOptionStep:
l.renderExternalNotFoundOption(w, r, authReq, err)
default:
l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-ds3QF", "step no possible"))
}
@@ -204,11 +223,12 @@ func (l *Login) getUserData(r *http.Request, authReq *model.AuthRequest, title s
return userData{
baseData: l.getBaseData(r, authReq, title, errType, errMessage),
profileData: l.getProfileData(authReq),
Linking: len(authReq.LinkingUsers) > 0,
}
}
func (l *Login) getBaseData(r *http.Request, authReq *model.AuthRequest, title string, errType, errMessage string) baseData {
return baseData{
baseData := baseData{
errorData: errorData{
ErrType: errType,
ErrMessage: errMessage,
@@ -217,10 +237,16 @@ func (l *Login) getBaseData(r *http.Request, authReq *model.AuthRequest, title s
Title: title,
Theme: l.getTheme(r),
ThemeMode: l.getThemeMode(r),
OrgID: l.getOrgID(authReq),
AuthReqID: getRequestID(authReq, r),
CSRF: csrf.TemplateField(r),
Nonce: http_mw.GetNonce(r),
}
if authReq != nil {
baseData.LoginPolicy = authReq.LoginPolicy
baseData.IDPProviders = authReq.AllowedExternalIDPs
}
return baseData
}
func (l *Login) getProfileData(authReq *model.AuthRequest) profileData {
@@ -253,6 +279,19 @@ func (l *Login) getThemeMode(r *http.Request) string {
return "" //TODO: impl
}
func (l *Login) getOrgID(authReq *model.AuthRequest) string {
if authReq == nil {
return ""
}
if authReq.UserOrgID != "" {
return authReq.UserOrgID
}
if authReq.Request == nil {
return ""
}
return authReq.GetScopeOrgID()
}
func getRequestID(authReq *model.AuthRequest, r *http.Request) string {
if authReq != nil {
return authReq.ID
@@ -275,13 +314,16 @@ func (l *Login) cspErrorHandler(err error) http.Handler {
type baseData struct {
errorData
Lang string
Title string
Theme string
ThemeMode string
AuthReqID string
CSRF template.HTML
Nonce string
Lang string
Title string
Theme string
ThemeMode string
OrgID string
AuthReqID string
CSRF template.HTML
Nonce string
LoginPolicy *iam_model.LoginPolicyView
IDPProviders []*iam_model.IDPProviderView
}
type errorData struct {
@@ -295,6 +337,7 @@ type userData struct {
PasswordChecked string
MfaProviders []model.MfaType
SelectedMfaProvider model.MfaType
Linking bool
}
type profileData struct {
@@ -315,7 +358,8 @@ type passwordData struct {
type userSelectionData struct {
baseData
Users []model.UserSelection
Users []model.UserSelection
Linking bool
}
type mfaData struct {
+33 -20
View File
@@ -7,26 +7,32 @@ import (
)
const (
EndpointRoot = "/"
EndpointHealthz = "/healthz"
EndpointReadiness = "/ready"
EndpointLogin = "/login"
EndpointLoginName = "/loginname"
EndpointUserSelection = "/userselection"
EndpointChangeUsername = "/username/change"
EndpointPassword = "/password"
EndpointInitPassword = "/password/init"
EndpointChangePassword = "/password/change"
EndpointPasswordReset = "/password/reset"
EndpointInitUser = "/user/init"
EndpointMfaVerify = "/mfa/verify"
EndpointMfaPrompt = "/mfa/prompt"
EndpointMfaInitVerify = "/mfa/init/verify"
EndpointMailVerification = "/mail/verification"
EndpointMailVerified = "/mail/verified"
EndpointRegister = "/register"
EndpointRegisterOrg = "/register/org"
EndpointLogoutDone = "/logout/done"
EndpointRoot = "/"
EndpointHealthz = "/healthz"
EndpointReadiness = "/ready"
EndpointLogin = "/login"
EndpointExternalLogin = "/login/externalidp"
EndpointExternalLoginCallback = "/login/externalidp/callback"
EndpointLoginName = "/loginname"
EndpointUserSelection = "/userselection"
EndpointChangeUsername = "/username/change"
EndpointPassword = "/password"
EndpointInitPassword = "/password/init"
EndpointChangePassword = "/password/change"
EndpointPasswordReset = "/password/reset"
EndpointInitUser = "/user/init"
EndpointMfaVerify = "/mfa/verify"
EndpointMfaPrompt = "/mfa/prompt"
EndpointMfaInitVerify = "/mfa/init/verify"
EndpointMailVerification = "/mail/verification"
EndpointMailVerified = "/mail/verified"
EndpointRegisterOption = "/register/option"
EndpointRegister = "/register"
EndpointExternalRegister = "/register/externalidp"
EndpointExternalRegisterCallback = "/register/externalidp/callback"
EndpointRegisterOrg = "/register/org"
EndpointLogoutDone = "/logout/done"
EndpointExternalNotFoundOption = "/externaluser/option"
EndpointResources = "/resources"
)
@@ -38,6 +44,8 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
router.HandleFunc(EndpointHealthz, login.handleHealthz).Methods(http.MethodGet)
router.HandleFunc(EndpointReadiness, login.handleReadiness).Methods(http.MethodGet)
router.HandleFunc(EndpointLogin, login.handleLogin).Methods(http.MethodGet, http.MethodPost)
router.HandleFunc(EndpointExternalLogin, login.handleExternalLogin).Methods(http.MethodGet)
router.HandleFunc(EndpointExternalLoginCallback, login.handleExternalLoginCallback).Methods(http.MethodGet)
router.HandleFunc(EndpointLoginName, login.handleLoginName).Methods(http.MethodGet)
router.HandleFunc(EndpointLoginName, login.handleLoginNameCheck).Methods(http.MethodPost)
router.HandleFunc(EndpointUserSelection, login.handleSelectUser).Methods(http.MethodPost)
@@ -55,8 +63,13 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
router.HandleFunc(EndpointMailVerification, login.handleMailVerification).Methods(http.MethodGet)
router.HandleFunc(EndpointMailVerification, login.handleMailVerificationCheck).Methods(http.MethodPost)
router.HandleFunc(EndpointChangePassword, login.handleChangePassword).Methods(http.MethodPost)
router.HandleFunc(EndpointRegisterOption, login.handleRegisterOption).Methods(http.MethodGet)
router.HandleFunc(EndpointRegisterOption, login.handleRegisterOptionCheck).Methods(http.MethodPost)
router.HandleFunc(EndpointExternalNotFoundOption, login.handleExternalNotFoundOptionCheck).Methods(http.MethodPost)
router.HandleFunc(EndpointRegister, login.handleRegister).Methods(http.MethodGet)
router.HandleFunc(EndpointRegister, login.handleRegisterCheck).Methods(http.MethodPost)
router.HandleFunc(EndpointExternalRegister, login.handleExternalRegister).Methods(http.MethodGet)
router.HandleFunc(EndpointExternalRegisterCallback, login.handleExternalRegisterCallback).Methods(http.MethodGet)
router.HandleFunc(EndpointLogoutDone, login.handleLogoutDone).Methods(http.MethodGet)
router.PathPrefix(EndpointResources).Handler(login.handleResources(staticDir)).Methods(http.MethodGet)
router.HandleFunc(EndpointRegisterOrg, login.handleRegisterOrg).Methods(http.MethodGet)
@@ -19,6 +19,7 @@ func (l *Login) renderUserSelection(w http.ResponseWriter, r *http.Request, auth
data := userSelectionData{
baseData: l.getBaseData(r, authReq, "Select User", "", ""),
Users: selectionData.Users,
Linking: len(authReq.LinkingUsers) > 0,
}
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplUserSelection], data, nil)
}