mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-13 03:24:26 +00:00
fix(app): move queries to query package (#2612)
* fix: move queries to query package * fix(auth): switch project role requests to query pkg * refactor: delete unused project role code * remove repo * implement sql queries * fix(database): oidc config change type to int2 * fix(queries): implement app queries * refactor: simplify code * fix: correct app query * Update app.go * fix token check * fix mock * test: app prepares * test: oidc compliance * test: OIDCOriginAllowList * fix: converter * resolve unsupported oidc version Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
parent
a9035def0f
commit
3473156c7e
@ -47,7 +47,7 @@ type API struct {
|
||||
type health interface {
|
||||
Health(ctx context.Context) error
|
||||
IamByID(ctx context.Context) (*iam_model.IAM, error)
|
||||
VerifierClientID(ctx context.Context, appName string) (string, error)
|
||||
VerifierClientID(ctx context.Context, appName string) (string, string, error)
|
||||
}
|
||||
|
||||
type auth interface {
|
||||
@ -159,7 +159,7 @@ func handleValidate(checks []ValidationFunction) func(w http.ResponseWriter, r *
|
||||
}
|
||||
|
||||
func (a *API) handleClientID(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := a.health.VerifierClientID(r.Context(), "Zitadel Console")
|
||||
id, _, err := a.health.VerifierClientID(r.Context(), "Zitadel Console")
|
||||
if err != nil {
|
||||
http_util.MarshalJSON(w, nil, err, http.StatusPreconditionFailed)
|
||||
return
|
||||
|
@ -15,7 +15,7 @@ type testVerifier struct {
|
||||
memberships []*Membership
|
||||
}
|
||||
|
||||
func (v *testVerifier) VerifyAccessToken(ctx context.Context, token, clientID string) (string, string, string, string, string, error) {
|
||||
func (v *testVerifier) VerifyAccessToken(ctx context.Context, token, clientID, projectID string) (string, string, string, string, string, error) {
|
||||
return "userID", "agentID", "clientID", "de", "orgID", nil
|
||||
}
|
||||
func (v *testVerifier) SearchMyMemberships(ctx context.Context) ([]*Membership, error) {
|
||||
@ -30,8 +30,8 @@ func (v *testVerifier) ExistsOrg(ctx context.Context, orgID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *testVerifier) VerifierClientID(ctx context.Context, appName string) (string, error) {
|
||||
return "clientID", nil
|
||||
func (v *testVerifier) VerifierClientID(ctx context.Context, appName string) (string, string, error) {
|
||||
return "clientID", "projectID", nil
|
||||
}
|
||||
|
||||
func (v *testVerifier) CheckOrgFeatures(context.Context, string, ...string) error {
|
||||
|
@ -20,8 +20,8 @@ type TokenVerifier struct {
|
||||
}
|
||||
|
||||
type authZRepo interface {
|
||||
VerifyAccessToken(ctx context.Context, token, verifierClientID string) (userID, agentID, clientID, prefLang, resourceOwner string, err error)
|
||||
VerifierClientID(ctx context.Context, name string) (clientID string, err error)
|
||||
VerifyAccessToken(ctx context.Context, token, verifierClientID, projectID string) (userID, agentID, clientID, prefLang, resourceOwner string, err error)
|
||||
VerifierClientID(ctx context.Context, name string) (clientID, projectID string, err error)
|
||||
SearchMyMemberships(ctx context.Context) ([]*Membership, error)
|
||||
ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (projectID string, origins []string, err error)
|
||||
ExistsOrg(ctx context.Context, orgID string) error
|
||||
@ -33,17 +33,18 @@ func Start(authZRepo authZRepo) (v *TokenVerifier) {
|
||||
}
|
||||
|
||||
func (v *TokenVerifier) VerifyAccessToken(ctx context.Context, token string, method string) (userID, clientID, agentID, prefLang, resourceOwner string, err error) {
|
||||
verifierClientID, err := v.clientIDFromMethod(ctx, method)
|
||||
verifierClientID, projectID, err := v.clientIDAndProjectIDFromMethod(ctx, method)
|
||||
if err != nil {
|
||||
return "", "", "", "", "", err
|
||||
}
|
||||
userID, agentID, clientID, prefLang, resourceOwner, err = v.authZRepo.VerifyAccessToken(ctx, token, verifierClientID)
|
||||
userID, agentID, clientID, prefLang, resourceOwner, err = v.authZRepo.VerifyAccessToken(ctx, token, verifierClientID, projectID)
|
||||
return userID, clientID, agentID, prefLang, resourceOwner, err
|
||||
}
|
||||
|
||||
type client struct {
|
||||
id string
|
||||
name string
|
||||
id string
|
||||
projectID string
|
||||
name string
|
||||
}
|
||||
|
||||
func (v *TokenVerifier) RegisterServer(appName, methodPrefix string, mappings MethodMapping) {
|
||||
@ -64,28 +65,28 @@ func prefixFromMethod(method string) (string, bool) {
|
||||
return parts[1], true
|
||||
}
|
||||
|
||||
func (v *TokenVerifier) clientIDFromMethod(ctx context.Context, method string) (_ string, err error) {
|
||||
func (v *TokenVerifier) clientIDAndProjectIDFromMethod(ctx context.Context, method string) (clientID, projectID string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
prefix, ok := prefixFromMethod(method)
|
||||
if !ok {
|
||||
return "", caos_errs.ThrowPermissionDenied(nil, "AUTHZ-GRD2Q", "Errors.Internal")
|
||||
return "", "", caos_errs.ThrowPermissionDenied(nil, "AUTHZ-GRD2Q", "Errors.Internal")
|
||||
}
|
||||
app, ok := v.clients.Load(prefix)
|
||||
if !ok {
|
||||
return "", caos_errs.ThrowPermissionDenied(nil, "AUTHZ-G2qrh", "Errors.Internal")
|
||||
return "", "", caos_errs.ThrowPermissionDenied(nil, "AUTHZ-G2qrh", "Errors.Internal")
|
||||
}
|
||||
c := app.(*client)
|
||||
if c.id != "" {
|
||||
return c.id, nil
|
||||
return c.id, c.projectID, nil
|
||||
}
|
||||
c.id, err = v.authZRepo.VerifierClientID(ctx, c.name)
|
||||
c.id, c.projectID, err = v.authZRepo.VerifierClientID(ctx, c.name)
|
||||
if err != nil {
|
||||
return "", caos_errs.ThrowPermissionDenied(err, "AUTHZ-ptTIF2", "Errors.Internal")
|
||||
return "", "", caos_errs.ThrowPermissionDenied(err, "AUTHZ-ptTIF2", "Errors.Internal")
|
||||
}
|
||||
v.clients.Store(prefix, c)
|
||||
return c.id, nil
|
||||
return c.id, c.projectID, nil
|
||||
}
|
||||
func (v *TokenVerifier) SearchMyMemberships(ctx context.Context) (_ []*Membership, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *Server) GetAppByID(ctx context.Context, req *mgmt_pb.GetAppByIDRequest) (*mgmt_pb.GetAppByIDResponse, error) {
|
||||
app, err := s.project.ApplicationByID(ctx, req.ProjectId, req.AppId)
|
||||
app, err := s.query.AppByProjectAndAppID(ctx, req.ProjectId, req.AppId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -26,16 +26,16 @@ func (s *Server) ListApps(ctx context.Context, req *mgmt_pb.ListAppsRequest) (*m
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domains, err := s.project.SearchApplications(ctx, queries)
|
||||
apps, err := s.query.SearchApps(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.ListAppsResponse{
|
||||
Result: project_grpc.AppsToPb(domains.Result),
|
||||
Result: project_grpc.AppsToPb(apps.Apps),
|
||||
Details: object_grpc.ToListDetails(
|
||||
domains.TotalResult,
|
||||
domains.Sequence,
|
||||
domains.Timestamp,
|
||||
apps.Count,
|
||||
apps.Sequence,
|
||||
apps.Timestamp,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
@ -196,16 +196,16 @@ func (s *Server) ListAppKeys(ctx context.Context, req *mgmt_pb.ListAppKeysReques
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domains, err := s.project.SearchClientKeys(ctx, queries)
|
||||
keys, err := s.project.SearchClientKeys(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.ListAppKeysResponse{
|
||||
Result: authn_grpc.KeyViewsToPb(domains.Result),
|
||||
Result: authn_grpc.KeyViewsToPb(keys.Result),
|
||||
Details: object_grpc.ToListDetails(
|
||||
domains.TotalResult,
|
||||
domains.Sequence,
|
||||
domains.Timestamp,
|
||||
keys.TotalResult,
|
||||
keys.Sequence,
|
||||
keys.Timestamp,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
@ -9,25 +9,27 @@ import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
key_model "github.com/caos/zitadel/internal/key/model"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
|
||||
)
|
||||
|
||||
func ListAppsRequestToModel(req *mgmt_pb.ListAppsRequest) (*proj_model.ApplicationSearchRequest, error) {
|
||||
func ListAppsRequestToModel(req *mgmt_pb.ListAppsRequest) (*query.AppSearchQueries, error) {
|
||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||
queries, err := app_grpc.AppQueriesToModel(req.Queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries = append(queries, &proj_model.ApplicationSearchQuery{
|
||||
Key: proj_model.AppSearchKeyProjectID,
|
||||
Method: domain.SearchMethodEquals,
|
||||
Value: req.ProjectId,
|
||||
})
|
||||
return &proj_model.ApplicationSearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
projectQuery, err := query.NewAppProjectIDSearchQuery(req.ProjectId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries = append(queries, projectQuery)
|
||||
return &query.AppSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
},
|
||||
//SortingColumn: //TODO: sorting
|
||||
Queries: queries,
|
||||
}, nil
|
||||
|
@ -7,11 +7,12 @@ import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
app_pb "github.com/caos/zitadel/pkg/grpc/app"
|
||||
message_pb "github.com/caos/zitadel/pkg/grpc/message"
|
||||
)
|
||||
|
||||
func AppsToPb(apps []*proj_model.ApplicationView) []*app_pb.App {
|
||||
func AppsToPb(apps []*query.App) []*app_pb.App {
|
||||
a := make([]*app_pb.App, len(apps))
|
||||
for i, app := range apps {
|
||||
a[i] = AppToPb(app)
|
||||
@ -19,7 +20,7 @@ func AppsToPb(apps []*proj_model.ApplicationView) []*app_pb.App {
|
||||
return a
|
||||
}
|
||||
|
||||
func AppToPb(app *proj_model.ApplicationView) *app_pb.App {
|
||||
func AppToPb(app *query.App) *app_pb.App {
|
||||
return &app_pb.App{
|
||||
Id: app.ID,
|
||||
Details: object_grpc.ToViewDetailsPb(app.Sequence, app.CreationDate, app.ChangeDate, app.ResourceOwner),
|
||||
@ -29,68 +30,68 @@ func AppToPb(app *proj_model.ApplicationView) *app_pb.App {
|
||||
}
|
||||
}
|
||||
|
||||
func AppConfigToPb(app *proj_model.ApplicationView) app_pb.AppConfig {
|
||||
if app.IsOIDC {
|
||||
return AppOIDCConfigToPb(app)
|
||||
func AppConfigToPb(app *query.App) app_pb.AppConfig {
|
||||
if app.OIDCConfig != nil {
|
||||
return AppOIDCConfigToPb(app.OIDCConfig)
|
||||
}
|
||||
return AppAPIConfigToPb(app)
|
||||
return AppAPIConfigToPb(app.APIConfig)
|
||||
}
|
||||
|
||||
func AppOIDCConfigToPb(app *proj_model.ApplicationView) *app_pb.App_OidcConfig {
|
||||
func AppOIDCConfigToPb(app *query.OIDCApp) *app_pb.App_OidcConfig {
|
||||
return &app_pb.App_OidcConfig{
|
||||
OidcConfig: &app_pb.OIDCConfig{
|
||||
RedirectUris: app.OIDCRedirectUris,
|
||||
ResponseTypes: OIDCResponseTypesFromModel(app.OIDCResponseTypes),
|
||||
GrantTypes: OIDCGrantTypesFromModel(app.OIDCGrantTypes),
|
||||
AppType: OIDCApplicationTypeToPb(domain.OIDCApplicationType(app.OIDCApplicationType)),
|
||||
ClientId: app.OIDCClientID,
|
||||
AuthMethodType: OIDCAuthMethodTypeToPb(domain.OIDCAuthMethodType(app.OIDCAuthMethodType)),
|
||||
PostLogoutRedirectUris: app.OIDCPostLogoutRedirectUris,
|
||||
Version: OIDCVersionToPb(domain.OIDCVersion(app.OIDCVersion)),
|
||||
NoneCompliant: app.NoneCompliant,
|
||||
RedirectUris: app.RedirectURIs,
|
||||
ResponseTypes: OIDCResponseTypesFromModel(app.ResponseTypes),
|
||||
GrantTypes: OIDCGrantTypesFromModel(app.GrantTypes),
|
||||
AppType: OIDCApplicationTypeToPb(app.AppType),
|
||||
ClientId: app.ClientID,
|
||||
AuthMethodType: OIDCAuthMethodTypeToPb(app.AuthMethodType),
|
||||
PostLogoutRedirectUris: app.PostLogoutRedirectURIs,
|
||||
Version: OIDCVersionToPb(domain.OIDCVersion(app.Version)),
|
||||
NoneCompliant: len(app.ComplianceProblems) != 0,
|
||||
ComplianceProblems: ComplianceProblemsToLocalizedMessages(app.ComplianceProblems),
|
||||
DevMode: app.DevMode,
|
||||
AccessTokenType: oidcTokenTypeToPb(domain.OIDCTokenType(app.AccessTokenType)),
|
||||
AccessTokenRoleAssertion: app.AccessTokenRoleAssertion,
|
||||
IdTokenRoleAssertion: app.IDTokenRoleAssertion,
|
||||
IdTokenUserinfoAssertion: app.IDTokenUserinfoAssertion,
|
||||
DevMode: app.IsDevMode,
|
||||
AccessTokenType: oidcTokenTypeToPb(app.AccessTokenType),
|
||||
AccessTokenRoleAssertion: app.AssertAccessTokenRole,
|
||||
IdTokenRoleAssertion: app.AssertIDTokenRole,
|
||||
IdTokenUserinfoAssertion: app.AssertIDTokenUserinfo,
|
||||
ClockSkew: durationpb.New(app.ClockSkew),
|
||||
AdditionalOrigins: app.AdditionalOrigins,
|
||||
AllowedOrigins: app.OriginAllowList,
|
||||
AllowedOrigins: app.AllowedOrigins,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func AppAPIConfigToPb(app *proj_model.ApplicationView) app_pb.AppConfig {
|
||||
func AppAPIConfigToPb(app *query.APIApp) app_pb.AppConfig {
|
||||
return &app_pb.App_ApiConfig{
|
||||
ApiConfig: &app_pb.APIConfig{
|
||||
ClientId: app.OIDCClientID,
|
||||
ClientId: app.ClientID,
|
||||
ClientSecret: "", //TODO: remove from proto
|
||||
AuthMethodType: APIAuthMethodeTypeToPb(domain.APIAuthMethodType(app.OIDCAuthMethodType)),
|
||||
AuthMethodType: APIAuthMethodeTypeToPb(app.AuthMethodType),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func AppStateToPb(state proj_model.AppState) app_pb.AppState {
|
||||
func AppStateToPb(state domain.AppState) app_pb.AppState {
|
||||
switch state {
|
||||
case proj_model.AppStateActive:
|
||||
case domain.AppStateActive:
|
||||
return app_pb.AppState_APP_STATE_ACTIVE
|
||||
case proj_model.AppStateInactive:
|
||||
case domain.AppStateInactive:
|
||||
return app_pb.AppState_APP_STATE_INACTIVE
|
||||
default:
|
||||
return app_pb.AppState_APP_STATE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func OIDCResponseTypesFromModel(responseTypes []proj_model.OIDCResponseType) []app_pb.OIDCResponseType {
|
||||
func OIDCResponseTypesFromModel(responseTypes []domain.OIDCResponseType) []app_pb.OIDCResponseType {
|
||||
oidcResponseTypes := make([]app_pb.OIDCResponseType, len(responseTypes))
|
||||
for i, responseType := range responseTypes {
|
||||
switch responseType {
|
||||
case proj_model.OIDCResponseTypeCode:
|
||||
case domain.OIDCResponseTypeCode:
|
||||
oidcResponseTypes[i] = app_pb.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE
|
||||
case proj_model.OIDCResponseTypeIDToken:
|
||||
case domain.OIDCResponseTypeIDToken:
|
||||
oidcResponseTypes[i] = app_pb.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN
|
||||
case proj_model.OIDCResponseTypeIDTokenToken:
|
||||
case domain.OIDCResponseTypeIDTokenToken:
|
||||
oidcResponseTypes[i] = app_pb.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN_TOKEN
|
||||
}
|
||||
}
|
||||
@ -98,7 +99,7 @@ func OIDCResponseTypesFromModel(responseTypes []proj_model.OIDCResponseType) []a
|
||||
}
|
||||
|
||||
func OIDCResponseTypesToDomain(responseTypes []app_pb.OIDCResponseType) []domain.OIDCResponseType {
|
||||
if responseTypes == nil || len(responseTypes) == 0 {
|
||||
if len(responseTypes) == 0 {
|
||||
return []domain.OIDCResponseType{domain.OIDCResponseTypeCode}
|
||||
}
|
||||
oidcResponseTypes := make([]domain.OIDCResponseType, len(responseTypes))
|
||||
@ -115,15 +116,15 @@ func OIDCResponseTypesToDomain(responseTypes []app_pb.OIDCResponseType) []domain
|
||||
return oidcResponseTypes
|
||||
}
|
||||
|
||||
func OIDCGrantTypesFromModel(grantTypes []proj_model.OIDCGrantType) []app_pb.OIDCGrantType {
|
||||
func OIDCGrantTypesFromModel(grantTypes []domain.OIDCGrantType) []app_pb.OIDCGrantType {
|
||||
oidcGrantTypes := make([]app_pb.OIDCGrantType, len(grantTypes))
|
||||
for i, grantType := range grantTypes {
|
||||
switch grantType {
|
||||
case proj_model.OIDCGrantTypeAuthorizationCode:
|
||||
case domain.OIDCGrantTypeAuthorizationCode:
|
||||
oidcGrantTypes[i] = app_pb.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE
|
||||
case proj_model.OIDCGrantTypeImplicit:
|
||||
case domain.OIDCGrantTypeImplicit:
|
||||
oidcGrantTypes[i] = app_pb.OIDCGrantType_OIDC_GRANT_TYPE_IMPLICIT
|
||||
case proj_model.OIDCGrantTypeRefreshToken:
|
||||
case domain.OIDCGrantTypeRefreshToken:
|
||||
oidcGrantTypes[i] = app_pb.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN
|
||||
}
|
||||
}
|
||||
@ -272,8 +273,8 @@ func APIAuthMethodTypeToDomain(authType app_pb.APIAuthMethodType) domain.APIAuth
|
||||
}
|
||||
}
|
||||
|
||||
func AppQueriesToModel(queries []*app_pb.AppQuery) (_ []*proj_model.ApplicationSearchQuery, err error) {
|
||||
q := make([]*proj_model.ApplicationSearchQuery, len(queries))
|
||||
func AppQueriesToModel(queries []*app_pb.AppQuery) (q []query.SearchQuery, err error) {
|
||||
q = make([]query.SearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
q[i], err = AppQueryToModel(query)
|
||||
if err != nil {
|
||||
@ -283,10 +284,10 @@ func AppQueriesToModel(queries []*app_pb.AppQuery) (_ []*proj_model.ApplicationS
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func AppQueryToModel(query *app_pb.AppQuery) (*proj_model.ApplicationSearchQuery, error) {
|
||||
switch q := query.Query.(type) {
|
||||
func AppQueryToModel(appQuery *app_pb.AppQuery) (query.SearchQuery, error) {
|
||||
switch q := appQuery.Query.(type) {
|
||||
case *app_pb.AppQuery_NameQuery:
|
||||
return AppQueryNameToModel(q.NameQuery), nil
|
||||
return query.NewAppNameSearchQuery(object_grpc.TextMethodToQuery(q.NameQuery.Method), q.NameQuery.Name)
|
||||
default:
|
||||
return nil, errors.ThrowInvalidArgument(nil, "APP-Add46", "List.Query.Invalid")
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ var (
|
||||
|
||||
type verifierMock struct{}
|
||||
|
||||
func (v *verifierMock) VerifyAccessToken(ctx context.Context, token, clientID string) (string, string, string, string, string, error) {
|
||||
func (v *verifierMock) VerifyAccessToken(ctx context.Context, token, clientID, projectID string) (string, string, string, string, string, error) {
|
||||
return "", "", "", "", "", nil
|
||||
}
|
||||
func (v *verifierMock) SearchMyMemberships(ctx context.Context) ([]*authz.Membership, error) {
|
||||
@ -34,8 +34,8 @@ func (v *verifierMock) ProjectIDAndOriginsByClientID(ctx context.Context, client
|
||||
func (v *verifierMock) ExistsOrg(ctx context.Context, orgID string) error {
|
||||
return nil
|
||||
}
|
||||
func (v *verifierMock) VerifierClientID(ctx context.Context, appName string) (string, error) {
|
||||
return "", nil
|
||||
func (v *verifierMock) VerifierClientID(ctx context.Context, appName string) (string, string, error) {
|
||||
return "", "", nil
|
||||
}
|
||||
func (v *verifierMock) CheckOrgFeatures(context.Context, string, ...string) error {
|
||||
return nil
|
||||
|
@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/caos/zitadel/internal/api/http/middleware"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||
@ -26,15 +25,20 @@ func (o *OPStorage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest
|
||||
if !ok {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-sd436", "no user agent id")
|
||||
}
|
||||
app, err := o.repo.ApplicationByClientID(ctx, req.ClientID)
|
||||
projectID, err := o.query.ProjectIDFromOIDCClientID(ctx, req.ClientID)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-AEG4d", "Errors.Internal")
|
||||
}
|
||||
req.Scopes, err = o.assertProjectRoleScopes(app, req.Scopes)
|
||||
project, err := o.query.ProjectByID(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-w4wIn", "Errors.Internal")
|
||||
}
|
||||
req.Scopes, err = o.assertProjectRoleScopes(project, req.Scopes)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-Gqrfg", "Errors.Internal")
|
||||
}
|
||||
authRequest := CreateAuthRequestToBusiness(ctx, req, userAgentID, userID)
|
||||
//TODO: ensure splitting of command and query side durring auth request and login refactoring
|
||||
resp, err := o.repo.CreateAuthRequest(ctx, authRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -205,8 +209,8 @@ func (o *OPStorage) GetKeySet(ctx context.Context) (_ *jose.JSONWebKeySet, err e
|
||||
return o.repo.GetKeySet(ctx)
|
||||
}
|
||||
|
||||
func (o *OPStorage) assertProjectRoleScopes(app *proj_model.ApplicationView, scopes []string) ([]string, error) {
|
||||
if !app.ProjectRoleAssertion {
|
||||
func (o *OPStorage) assertProjectRoleScopes(project *query.Project, scopes []string) ([]string, error) {
|
||||
if !project.ProjectRoleAssertion {
|
||||
return scopes, nil
|
||||
}
|
||||
for _, scope := range scopes {
|
||||
@ -214,7 +218,7 @@ func (o *OPStorage) assertProjectRoleScopes(app *proj_model.ApplicationView, sco
|
||||
return scopes, nil
|
||||
}
|
||||
}
|
||||
projectIDQuery, err := query.NewProjectRoleProjectIDSearchQuery(app.ProjectID)
|
||||
projectIDQuery, err := query.NewProjectRoleProjectIDSearchQuery(project.ID)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "OIDC-Cyc78", "Errors.Internal")
|
||||
}
|
||||
|
@ -12,11 +12,10 @@ import (
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/api/http"
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
authreq_model "github.com/caos/zitadel/internal/auth_request/model"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
user_model "github.com/caos/zitadel/internal/user/model"
|
||||
@ -37,11 +36,11 @@ const (
|
||||
func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (_ op.Client, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
client, err := o.repo.ApplicationByClientID(ctx, id)
|
||||
client, err := o.query.AppByOIDCClientID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if client.State != proj_model.AppStateActive {
|
||||
if client.State != domain.AppStateActive {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-sdaGg", "client is not active")
|
||||
}
|
||||
projectIDQuery, err := query.NewProjectRoleProjectIDSearchQuery(client.ProjectID)
|
||||
@ -125,11 +124,11 @@ func (o *OPStorage) SetUserinfoFromToken(ctx context.Context, userInfo oidc.User
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired")
|
||||
}
|
||||
if token.ApplicationID != "" {
|
||||
app, err := o.repo.ApplicationByClientID(ctx, token.ApplicationID)
|
||||
app, err := o.query.AppByOIDCClientID(ctx, token.ApplicationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if origin != "" && !http.IsOriginAllowed(app.OriginAllowList, origin) {
|
||||
if origin != "" && !http.IsOriginAllowed(app.OIDCConfig.AllowedOrigins, origin) {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed")
|
||||
}
|
||||
}
|
||||
@ -202,8 +201,8 @@ func (o *OPStorage) SetUserinfoFromScopes(ctx context.Context, userInfo oidc.Use
|
||||
if strings.HasPrefix(scope, ScopeProjectRolePrefix) {
|
||||
roles = append(roles, strings.TrimPrefix(scope, ScopeProjectRolePrefix))
|
||||
}
|
||||
if strings.HasPrefix(scope, model.OrgDomainPrimaryScope) {
|
||||
userInfo.AppendClaims(model.OrgDomainPrimaryClaim, strings.TrimPrefix(scope, model.OrgDomainPrimaryScope))
|
||||
if strings.HasPrefix(scope, authreq_model.OrgDomainPrimaryScope) {
|
||||
userInfo.AppendClaims(authreq_model.OrgDomainPrimaryClaim, strings.TrimPrefix(scope, authreq_model.OrgDomainPrimaryScope))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -225,12 +224,12 @@ func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection
|
||||
if err != nil {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired")
|
||||
}
|
||||
app, err := o.repo.ApplicationByClientID(ctx, clientID)
|
||||
projectID, err := o.query.ProjectIDFromOIDCClientID(ctx, clientID)
|
||||
if err != nil {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-Adfg5", "client not found")
|
||||
}
|
||||
for _, aud := range token.Audience {
|
||||
if aud == clientID || aud == app.ProjectID {
|
||||
if aud == clientID || aud == projectID {
|
||||
err := o.SetUserinfoFromScopes(ctx, introspection, token.UserID, clientID, token.Scopes)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -266,8 +265,8 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
|
||||
}
|
||||
if strings.HasPrefix(scope, ScopeProjectRolePrefix) {
|
||||
roles = append(roles, strings.TrimPrefix(scope, ScopeProjectRolePrefix))
|
||||
} else if strings.HasPrefix(scope, model.OrgDomainPrimaryScope) {
|
||||
claims = appendClaim(claims, model.OrgDomainPrimaryClaim, strings.TrimPrefix(scope, model.OrgDomainPrimaryScope))
|
||||
} else if strings.HasPrefix(scope, authreq_model.OrgDomainPrimaryScope) {
|
||||
claims = appendClaim(claims, authreq_model.OrgDomainPrimaryClaim, strings.TrimPrefix(scope, authreq_model.OrgDomainPrimaryScope))
|
||||
}
|
||||
}
|
||||
if len(roles) == 0 || clientID == "" {
|
||||
@ -284,11 +283,11 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
|
||||
}
|
||||
|
||||
func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID string, requestedRoles []string) (map[string]map[string]string, error) {
|
||||
app, err := o.repo.ApplicationByClientID(ctx, applicationID)
|
||||
projectID, err := o.query.ProjectIDFromOIDCClientID(ctx, applicationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
grants, err := o.repo.UserGrantsByProjectAndUserID(app.ProjectID, userID)
|
||||
grants, err := o.repo.UserGrantsByProjectAndUserID(projectID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -8,24 +8,25 @@ import (
|
||||
"github.com/caos/oidc/pkg/op"
|
||||
|
||||
authreq_model "github.com/caos/zitadel/internal/auth_request/model"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/project/model"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
*model.ApplicationView
|
||||
app *query.App
|
||||
defaultLoginURL string
|
||||
defaultAccessTokenLifetime time.Duration
|
||||
defaultIdTokenLifetime time.Duration
|
||||
allowedScopes []string
|
||||
}
|
||||
|
||||
func ClientFromBusiness(app *model.ApplicationView, defaultLoginURL string, defaultAccessTokenLifetime, defaultIdTokenLifetime time.Duration, allowedScopes []string) (op.Client, error) {
|
||||
if !app.IsOIDC {
|
||||
func ClientFromBusiness(app *query.App, defaultLoginURL string, defaultAccessTokenLifetime, defaultIdTokenLifetime time.Duration, allowedScopes []string) (op.Client, error) {
|
||||
if app.OIDCConfig == nil {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "OIDC-d5bhD", "client is not a proper oidc application")
|
||||
}
|
||||
return &Client{
|
||||
ApplicationView: app,
|
||||
app: app,
|
||||
defaultLoginURL: defaultLoginURL,
|
||||
defaultAccessTokenLifetime: defaultAccessTokenLifetime,
|
||||
defaultIdTokenLifetime: defaultIdTokenLifetime,
|
||||
@ -34,15 +35,15 @@ func ClientFromBusiness(app *model.ApplicationView, defaultLoginURL string, defa
|
||||
}
|
||||
|
||||
func (c *Client) ApplicationType() op.ApplicationType {
|
||||
return op.ApplicationType(c.OIDCApplicationType)
|
||||
return op.ApplicationType(c.app.OIDCConfig.AppType)
|
||||
}
|
||||
|
||||
func (c *Client) AuthMethod() oidc.AuthMethod {
|
||||
return authMethodToOIDC(c.OIDCAuthMethodType)
|
||||
return authMethodToOIDC(c.app.OIDCConfig.AuthMethodType)
|
||||
}
|
||||
|
||||
func (c *Client) GetID() string {
|
||||
return c.OIDCClientID
|
||||
return c.app.OIDCConfig.ClientID
|
||||
}
|
||||
|
||||
func (c *Client) LoginURL(id string) string {
|
||||
@ -50,28 +51,28 @@ func (c *Client) LoginURL(id string) string {
|
||||
}
|
||||
|
||||
func (c *Client) RedirectURIs() []string {
|
||||
return c.OIDCRedirectUris
|
||||
return c.app.OIDCConfig.RedirectURIs
|
||||
}
|
||||
|
||||
func (c *Client) PostLogoutRedirectURIs() []string {
|
||||
return c.OIDCPostLogoutRedirectUris
|
||||
return c.app.OIDCConfig.PostLogoutRedirectURIs
|
||||
}
|
||||
|
||||
func (c *Client) ResponseTypes() []oidc.ResponseType {
|
||||
return responseTypesToOIDC(c.OIDCResponseTypes)
|
||||
return responseTypesToOIDC(c.app.OIDCConfig.ResponseTypes)
|
||||
}
|
||||
|
||||
func (c *Client) GrantTypes() []oidc.GrantType {
|
||||
return grantTypesToOIDC(c.OIDCGrantTypes)
|
||||
return grantTypesToOIDC(c.app.OIDCConfig.GrantTypes)
|
||||
}
|
||||
|
||||
func (c *Client) DevMode() bool {
|
||||
return c.ApplicationView.DevMode
|
||||
return c.app.OIDCConfig.IsDevMode
|
||||
}
|
||||
|
||||
func (c *Client) RestrictAdditionalIdTokenScopes() func(scopes []string) []string {
|
||||
return func(scopes []string) []string {
|
||||
if c.IDTokenRoleAssertion {
|
||||
if c.app.OIDCConfig.AssertIDTokenRole {
|
||||
return scopes
|
||||
}
|
||||
return removeScopeWithPrefix(scopes, ScopeProjectRolePrefix)
|
||||
@ -80,7 +81,7 @@ func (c *Client) RestrictAdditionalIdTokenScopes() func(scopes []string) []strin
|
||||
|
||||
func (c *Client) RestrictAdditionalAccessTokenScopes() func(scopes []string) []string {
|
||||
return func(scopes []string) []string {
|
||||
if c.AccessTokenRoleAssertion {
|
||||
if c.app.OIDCConfig.AssertAccessTokenRole {
|
||||
return scopes
|
||||
}
|
||||
return removeScopeWithPrefix(scopes, ScopeProjectRolePrefix)
|
||||
@ -96,7 +97,7 @@ func (c *Client) IDTokenLifetime() time.Duration {
|
||||
}
|
||||
|
||||
func (c *Client) AccessTokenType() op.AccessTokenType {
|
||||
return accessTokenTypeToOIDC(c.ApplicationView.AccessTokenType)
|
||||
return accessTokenTypeToOIDC(c.app.OIDCConfig.AccessTokenType)
|
||||
}
|
||||
|
||||
func (c *Client) IsScopeAllowed(scope string) bool {
|
||||
@ -124,40 +125,40 @@ func (c *Client) IsScopeAllowed(scope string) bool {
|
||||
}
|
||||
|
||||
func (c *Client) ClockSkew() time.Duration {
|
||||
return c.ApplicationView.ClockSkew
|
||||
return c.app.OIDCConfig.ClockSkew
|
||||
}
|
||||
|
||||
func (c *Client) IDTokenUserinfoClaimsAssertion() bool {
|
||||
return c.ApplicationView.IDTokenUserinfoAssertion
|
||||
return c.app.OIDCConfig.AssertIDTokenUserinfo
|
||||
}
|
||||
|
||||
func accessTokenTypeToOIDC(tokenType model.OIDCTokenType) op.AccessTokenType {
|
||||
func accessTokenTypeToOIDC(tokenType domain.OIDCTokenType) op.AccessTokenType {
|
||||
switch tokenType {
|
||||
case model.OIDCTokenTypeBearer:
|
||||
case domain.OIDCTokenTypeBearer:
|
||||
return op.AccessTokenTypeBearer
|
||||
case model.OIDCTokenTypeJWT:
|
||||
case domain.OIDCTokenTypeJWT:
|
||||
return op.AccessTokenTypeJWT
|
||||
default:
|
||||
return op.AccessTokenTypeBearer
|
||||
}
|
||||
}
|
||||
|
||||
func authMethodToOIDC(authType model.OIDCAuthMethodType) oidc.AuthMethod {
|
||||
func authMethodToOIDC(authType domain.OIDCAuthMethodType) oidc.AuthMethod {
|
||||
switch authType {
|
||||
case model.OIDCAuthMethodTypeBasic:
|
||||
case domain.OIDCAuthMethodTypeBasic:
|
||||
return oidc.AuthMethodBasic
|
||||
case model.OIDCAuthMethodTypePost:
|
||||
case domain.OIDCAuthMethodTypePost:
|
||||
return oidc.AuthMethodPost
|
||||
case model.OIDCAuthMethodTypeNone:
|
||||
case domain.OIDCAuthMethodTypeNone:
|
||||
return oidc.AuthMethodNone
|
||||
case model.OIDCAuthMethodTypePrivateKeyJWT:
|
||||
case domain.OIDCAuthMethodTypePrivateKeyJWT:
|
||||
return oidc.AuthMethodPrivateKeyJWT
|
||||
default:
|
||||
return oidc.AuthMethodBasic
|
||||
}
|
||||
}
|
||||
|
||||
func responseTypesToOIDC(responseTypes []model.OIDCResponseType) []oidc.ResponseType {
|
||||
func responseTypesToOIDC(responseTypes []domain.OIDCResponseType) []oidc.ResponseType {
|
||||
oidcTypes := make([]oidc.ResponseType, len(responseTypes))
|
||||
for i, t := range responseTypes {
|
||||
oidcTypes[i] = responseTypeToOIDC(t)
|
||||
@ -165,20 +166,20 @@ func responseTypesToOIDC(responseTypes []model.OIDCResponseType) []oidc.Response
|
||||
return oidcTypes
|
||||
}
|
||||
|
||||
func responseTypeToOIDC(responseType model.OIDCResponseType) oidc.ResponseType {
|
||||
func responseTypeToOIDC(responseType domain.OIDCResponseType) oidc.ResponseType {
|
||||
switch responseType {
|
||||
case model.OIDCResponseTypeCode:
|
||||
case domain.OIDCResponseTypeCode:
|
||||
return oidc.ResponseTypeCode
|
||||
case model.OIDCResponseTypeIDTokenToken:
|
||||
case domain.OIDCResponseTypeIDTokenToken:
|
||||
return oidc.ResponseTypeIDToken
|
||||
case model.OIDCResponseTypeIDToken:
|
||||
case domain.OIDCResponseTypeIDToken:
|
||||
return oidc.ResponseTypeIDTokenOnly
|
||||
default:
|
||||
return oidc.ResponseTypeCode
|
||||
}
|
||||
}
|
||||
|
||||
func grantTypesToOIDC(grantTypes []model.OIDCGrantType) []oidc.GrantType {
|
||||
func grantTypesToOIDC(grantTypes []domain.OIDCGrantType) []oidc.GrantType {
|
||||
oidcTypes := make([]oidc.GrantType, len(grantTypes))
|
||||
for i, t := range grantTypes {
|
||||
oidcTypes[i] = grantTypeToOIDC(t)
|
||||
@ -186,13 +187,13 @@ func grantTypesToOIDC(grantTypes []model.OIDCGrantType) []oidc.GrantType {
|
||||
return oidcTypes
|
||||
}
|
||||
|
||||
func grantTypeToOIDC(grantType model.OIDCGrantType) oidc.GrantType {
|
||||
func grantTypeToOIDC(grantType domain.OIDCGrantType) oidc.GrantType {
|
||||
switch grantType {
|
||||
case model.OIDCGrantTypeAuthorizationCode:
|
||||
case domain.OIDCGrantTypeAuthorizationCode:
|
||||
return oidc.GrantTypeCode
|
||||
case model.OIDCGrantTypeImplicit:
|
||||
case domain.OIDCGrantTypeImplicit:
|
||||
return oidc.GrantTypeImplicit
|
||||
case model.OIDCGrantTypeRefreshToken:
|
||||
case domain.OIDCGrantTypeRefreshToken:
|
||||
return oidc.GrantTypeRefreshToken
|
||||
default:
|
||||
return oidc.GrantTypeCode
|
||||
|
@ -2,11 +2,8 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/project/model"
|
||||
)
|
||||
|
||||
type ApplicationRepository interface {
|
||||
ApplicationByClientID(ctx context.Context, clientID string) (*model.ApplicationView, error)
|
||||
AuthorizeClientIDSecret(ctx context.Context, clientID, secret string) error
|
||||
}
|
||||
|
@ -3,35 +3,25 @@ package eventstore
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/project/model"
|
||||
proj_view_model "github.com/caos/zitadel/internal/project/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
type ApplicationRepo struct {
|
||||
Commands *command.Commands
|
||||
View *view.View
|
||||
}
|
||||
|
||||
func (a *ApplicationRepo) ApplicationByClientID(ctx context.Context, clientID string) (*model.ApplicationView, error) {
|
||||
app, err := a.View.ApplicationByClientID(ctx, clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proj_view_model.ApplicationViewToModel(app), nil
|
||||
Query *query.Queries
|
||||
}
|
||||
|
||||
func (a *ApplicationRepo) AuthorizeClientIDSecret(ctx context.Context, clientID, secret string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
app, err := a.View.ApplicationByClientID(ctx, clientID)
|
||||
app, err := a.Query.AppByOIDCClientID(ctx, clientID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if app.IsOIDC {
|
||||
if app.OIDCConfig != nil {
|
||||
return a.Commands.VerifyOIDCClientSecret(ctx, app.ProjectID, app.ID, secret)
|
||||
}
|
||||
return a.Commands.VerifyAPIClientSecret(ctx, app.ProjectID, app.ID, secret)
|
||||
|
@ -29,6 +29,7 @@ import (
|
||||
|
||||
type AuthRequestRepo struct {
|
||||
Command *command.Commands
|
||||
Query *query.Queries
|
||||
AuthRequests cache.AuthRequestCache
|
||||
View *view.View
|
||||
Eventstore v1.Eventstore
|
||||
@ -101,12 +102,12 @@ type orgViewProvider interface {
|
||||
}
|
||||
|
||||
type userGrantProvider interface {
|
||||
ApplicationByClientID(context.Context, string) (*project_view_model.ApplicationView, error)
|
||||
ProjectByOIDCClientID(context.Context, string) (*query.Project, error)
|
||||
UserGrantsByProjectAndUserID(string, string) ([]*grant_view_model.UserGrantView, error)
|
||||
}
|
||||
|
||||
type projectProvider interface {
|
||||
ApplicationByClientID(context.Context, string) (*project_view_model.ApplicationView, error)
|
||||
ProjectByOIDCClientID(context.Context, string) (*query.Project, error)
|
||||
OrgProjectMappingByIDs(orgID, projectID string) (*project_view_model.OrgProjectMapping, error)
|
||||
}
|
||||
|
||||
@ -122,18 +123,22 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *dom
|
||||
return nil, err
|
||||
}
|
||||
request.ID = reqID
|
||||
app, err := repo.View.ApplicationByClientID(ctx, request.ApplicationID)
|
||||
project, err := repo.ProjectProvider.ProjectByOIDCClientID(ctx, request.ApplicationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appIDs, err := repo.View.AppIDsFromProjectID(ctx, app.ProjectID)
|
||||
projectIDQuery, err := query.NewAppProjectIDSearchQuery(project.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appIDs, err := repo.Query.SearchAppIDs(ctx, &query.AppSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Audience = appIDs
|
||||
request.AppendAudIfNotExisting(app.ProjectID)
|
||||
request.ApplicationResourceOwner = app.ResourceOwner
|
||||
request.PrivateLabelingSetting = app.PrivateLabelingSetting
|
||||
request.AppendAudIfNotExisting(project.ID)
|
||||
request.ApplicationResourceOwner = project.ResourceOwner
|
||||
request.PrivateLabelingSetting = project.PrivateLabelingSetting
|
||||
if err := setOrgID(repo.OrgViewProvider, request); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1175,20 +1180,20 @@ func linkingIDPConfigExistingInAllowedIDPs(linkingUsers []*domain.ExternalUser,
|
||||
}
|
||||
|
||||
func userGrantRequired(ctx context.Context, request *domain.AuthRequest, user *user_model.UserView, userGrantProvider userGrantProvider) (_ bool, err error) {
|
||||
var app *project_view_model.ApplicationView
|
||||
var project *query.Project
|
||||
switch request.Request.Type() {
|
||||
case domain.AuthRequestTypeOIDC:
|
||||
app, err = userGrantProvider.ApplicationByClientID(ctx, request.ApplicationID)
|
||||
project, err = userGrantProvider.ProjectByOIDCClientID(ctx, request.ApplicationID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
default:
|
||||
return false, errors.ThrowPreconditionFailed(nil, "EVENT-dfrw2", "Errors.AuthRequest.RequestTypeNotSupported")
|
||||
}
|
||||
if !app.ProjectRoleCheck {
|
||||
if !project.ProjectRoleCheck {
|
||||
return false, nil
|
||||
}
|
||||
grants, err := userGrantProvider.UserGrantsByProjectAndUserID(app.ProjectID, user.ID)
|
||||
grants, err := userGrantProvider.UserGrantsByProjectAndUserID(project.ID, user.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -1196,20 +1201,20 @@ func userGrantRequired(ctx context.Context, request *domain.AuthRequest, user *u
|
||||
}
|
||||
|
||||
func projectRequired(ctx context.Context, request *domain.AuthRequest, projectProvider projectProvider) (_ bool, err error) {
|
||||
var app *project_view_model.ApplicationView
|
||||
var project *query.Project
|
||||
switch request.Request.Type() {
|
||||
case domain.AuthRequestTypeOIDC:
|
||||
app, err = projectProvider.ApplicationByClientID(ctx, request.ApplicationID)
|
||||
project, err = projectProvider.ProjectByOIDCClientID(ctx, request.ApplicationID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
default:
|
||||
return false, errors.ThrowPreconditionFailed(nil, "EVENT-dfrw2", "Errors.AuthRequest.RequestTypeNotSupported")
|
||||
}
|
||||
if !app.HasProjectCheck {
|
||||
if !project.HasProjectCheck {
|
||||
return false, nil
|
||||
}
|
||||
_, err = projectProvider.OrgProjectMappingByIDs(request.UserOrgID, app.ProjectID)
|
||||
_, err = projectProvider.OrgProjectMappingByIDs(request.UserOrgID, project.ID)
|
||||
if errors.IsNotFound(err) {
|
||||
return true, nil
|
||||
}
|
||||
|
@ -212,8 +212,8 @@ type mockUserGrants struct {
|
||||
userGrants int
|
||||
}
|
||||
|
||||
func (m *mockUserGrants) ApplicationByClientID(ctx context.Context, s string) (*proj_view_model.ApplicationView, error) {
|
||||
return &proj_view_model.ApplicationView{ProjectRoleCheck: m.roleCheck}, nil
|
||||
func (m *mockUserGrants) ProjectByOIDCClientID(ctx context.Context, s string) (*query.Project, error) {
|
||||
return &query.Project{ProjectRoleCheck: m.roleCheck}, nil
|
||||
}
|
||||
|
||||
func (m *mockUserGrants) UserGrantsByProjectAndUserID(s string, s2 string) ([]*grant_view_model.UserGrantView, error) {
|
||||
@ -229,8 +229,8 @@ type mockProject struct {
|
||||
projectCheck bool
|
||||
}
|
||||
|
||||
func (m *mockProject) ApplicationByClientID(ctx context.Context, s string) (*proj_view_model.ApplicationView, error) {
|
||||
return &proj_view_model.ApplicationView{HasProjectCheck: m.projectCheck}, nil
|
||||
func (m *mockProject) ProjectByOIDCClientID(ctx context.Context, s string) (*query.Project, error) {
|
||||
return &query.Project{HasProjectCheck: m.projectCheck}, nil
|
||||
}
|
||||
|
||||
func (m *mockProject) OrgProjectMappingByIDs(orgID, projectID string) (*proj_view_model.OrgProjectMapping, error) {
|
||||
|
@ -1,168 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/query"
|
||||
es_sdk "github.com/caos/zitadel/internal/eventstore/v1/sdk"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
|
||||
proj_view "github.com/caos/zitadel/internal/project/repository/view"
|
||||
view_model "github.com/caos/zitadel/internal/project/repository/view/model"
|
||||
)
|
||||
|
||||
const (
|
||||
applicationTable = "auth.applications"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
handler
|
||||
subscription *v1.Subscription
|
||||
}
|
||||
|
||||
func newApplication(handler handler) *Application {
|
||||
h := &Application{
|
||||
handler: handler,
|
||||
}
|
||||
|
||||
h.subscribe()
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (a *Application) subscribe() {
|
||||
a.subscription = a.es.Subscribe(a.AggregateTypes()...)
|
||||
go func() {
|
||||
for event := range a.subscription.Events {
|
||||
query.ReduceEvent(a, event)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (a *Application) ViewModel() string {
|
||||
return applicationTable
|
||||
}
|
||||
|
||||
func (a *Application) Subscription() *v1.Subscription {
|
||||
return a.subscription
|
||||
}
|
||||
|
||||
func (_ *Application) AggregateTypes() []models.AggregateType {
|
||||
return []models.AggregateType{es_model.ProjectAggregate}
|
||||
}
|
||||
|
||||
func (a *Application) CurrentSequence() (uint64, error) {
|
||||
sequence, err := a.view.GetLatestApplicationSequence()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return sequence.CurrentSequence, nil
|
||||
}
|
||||
|
||||
func (a *Application) EventQuery() (*models.SearchQuery, error) {
|
||||
sequence, err := a.view.GetLatestApplicationSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proj_view.ProjectQuery(sequence.CurrentSequence), nil
|
||||
}
|
||||
|
||||
func (a *Application) Reduce(event *models.Event) (err error) {
|
||||
app := new(view_model.ApplicationView)
|
||||
switch event.Type {
|
||||
case es_model.ApplicationAdded:
|
||||
project, err := a.getProjectByID(context.Background(), event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app.ProjectRoleCheck = project.ProjectRoleCheck
|
||||
app.HasProjectCheck = project.HasProjectCheck
|
||||
app.ProjectRoleAssertion = project.ProjectRoleAssertion
|
||||
app.PrivateLabelingSetting = project.PrivateLabelingSetting
|
||||
|
||||
err = app.AppendEvent(event)
|
||||
case es_model.ApplicationChanged,
|
||||
es_model.OIDCConfigAdded,
|
||||
es_model.OIDCConfigChanged,
|
||||
es_model.APIConfigAdded,
|
||||
es_model.APIConfigChanged,
|
||||
es_model.ApplicationDeactivated,
|
||||
es_model.ApplicationReactivated:
|
||||
err = app.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app, err = a.view.ApplicationByID(event.AggregateID, app.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = app.AppendEvent(event)
|
||||
case es_model.ApplicationRemoved:
|
||||
err = app.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.view.DeleteApplication(app.ID, event)
|
||||
case es_model.ProjectChanged:
|
||||
apps, err := a.view.ApplicationsByProjectID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(apps) == 0 {
|
||||
return a.view.ProcessedApplicationSequence(event)
|
||||
}
|
||||
for _, app := range apps {
|
||||
if err := app.AppendEvent(event); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return a.view.PutApplications(apps, event)
|
||||
case es_model.ProjectRemoved:
|
||||
err = a.view.DeleteApplicationsByProjectID(event.AggregateID)
|
||||
if err == nil {
|
||||
return a.view.ProcessedApplicationSequence(event)
|
||||
}
|
||||
default:
|
||||
return a.view.ProcessedApplicationSequence(event)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.view.PutApplication(app, event)
|
||||
}
|
||||
|
||||
func (a *Application) OnError(event *models.Event, spoolerError error) error {
|
||||
logging.LogWithFields("SPOOL-ls9ew", "id", event.AggregateID).WithError(spoolerError).Warn("something went wrong in project app handler")
|
||||
return spooler.HandleError(event, spoolerError, a.view.GetLatestApplicationFailedEvent, a.view.ProcessedApplicationFailedEvent, a.view.ProcessedApplicationSequence, a.errorCountUntilSkip)
|
||||
}
|
||||
|
||||
func (a *Application) OnSuccess() error {
|
||||
return spooler.HandleSuccess(a.view.UpdateApplicationSpoolerRunTimestamp)
|
||||
}
|
||||
|
||||
func (a *Application) getProjectByID(ctx context.Context, projID string) (*proj_model.Project, error) {
|
||||
query, err := proj_view.ProjectByIDQuery(projID, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
esProject := &es_model.Project{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: projID,
|
||||
},
|
||||
}
|
||||
err = es_sdk.Filter(ctx, a.Eventstore().FilterEvents, esProject.AppendEvents, query)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
if esProject.Sequence == 0 {
|
||||
return nil, errors.ThrowNotFound(nil, "EVENT-DBf32", "Errors.Project.NotFound")
|
||||
}
|
||||
|
||||
return es_model.ProjectToModel(esProject), nil
|
||||
}
|
@ -44,7 +44,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
|
||||
newKey(
|
||||
handler{view, bulkLimit, configs.cycleDuration("Key"), errorCount, es},
|
||||
keyChan),
|
||||
newApplication(handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount, es}),
|
||||
newUserGrant(
|
||||
handler{view, bulkLimit, configs.cycleDuration("UserGrant"), errorCount, es},
|
||||
systemDefaults.IamID),
|
||||
|
@ -68,7 +68,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co
|
||||
|
||||
assetsAPI := conf.APIDomain + "/assets/v1/"
|
||||
|
||||
view, err := auth_view.StartView(sqlClient, keyAlgorithm, idGenerator, assetsAPI)
|
||||
view, err := auth_view.StartView(sqlClient, keyAlgorithm, queries, idGenerator, assetsAPI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -92,6 +92,14 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co
|
||||
SystemDefaults: systemDefaults,
|
||||
PrefixAvatarURL: assetsAPI,
|
||||
}
|
||||
//TODO: remove as soon as possible
|
||||
queryView := struct {
|
||||
*query.Queries
|
||||
*auth_view.View
|
||||
}{
|
||||
queries,
|
||||
view,
|
||||
}
|
||||
return &EsRepository{
|
||||
spool,
|
||||
es,
|
||||
@ -111,8 +119,9 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co
|
||||
IDPProviderViewProvider: view,
|
||||
LockoutPolicyViewProvider: queries,
|
||||
LoginPolicyViewProvider: queries,
|
||||
UserGrantProvider: view,
|
||||
ProjectProvider: view,
|
||||
Query: queries,
|
||||
UserGrantProvider: queryView,
|
||||
ProjectProvider: queryView,
|
||||
IdGenerator: idGenerator,
|
||||
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
|
||||
ExternalLoginCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
|
||||
@ -143,7 +152,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co
|
||||
},
|
||||
eventstore.ApplicationRepo{
|
||||
Commands: command,
|
||||
View: view,
|
||||
Query: queries,
|
||||
},
|
||||
|
||||
eventstore.UserSessionRepo{
|
||||
|
@ -1,133 +0,0 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
"github.com/caos/zitadel/internal/project/repository/view"
|
||||
"github.com/caos/zitadel/internal/project/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/view/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
applicationTable = "auth.applications"
|
||||
)
|
||||
|
||||
func (v *View) ApplicationByID(projectID, appID string) (*model.ApplicationView, error) {
|
||||
return view.ApplicationByID(v.Db, applicationTable, projectID, appID)
|
||||
}
|
||||
|
||||
func (v *View) ApplicationsByProjectID(projectID string) ([]*model.ApplicationView, error) {
|
||||
return view.ApplicationsByProjectID(v.Db, applicationTable, projectID)
|
||||
}
|
||||
|
||||
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, uint64, error) {
|
||||
return view.SearchApplications(v.Db, applicationTable, request)
|
||||
}
|
||||
|
||||
func (v *View) PutApplication(app *model.ApplicationView, event *models.Event) error {
|
||||
err := view.PutApplication(v.Db, applicationTable, app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedApplicationSequence(event)
|
||||
}
|
||||
|
||||
func (v *View) PutApplications(apps []*model.ApplicationView, event *models.Event) error {
|
||||
err := view.PutApplications(v.Db, applicationTable, apps...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedApplicationSequence(event)
|
||||
}
|
||||
|
||||
func (v *View) DeleteApplication(appID string, event *models.Event) error {
|
||||
err := view.DeleteApplication(v.Db, applicationTable, appID)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedApplicationSequence(event)
|
||||
}
|
||||
|
||||
func (v *View) DeleteApplicationsByProjectID(projectID string) error {
|
||||
return view.DeleteApplicationsByProjectID(v.Db, applicationTable, projectID)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestApplicationSequence() (*repository.CurrentSequence, error) {
|
||||
return v.latestSequence(applicationTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedApplicationSequence(event *models.Event) error {
|
||||
return v.saveCurrentSequence(applicationTable, event)
|
||||
}
|
||||
|
||||
func (v *View) UpdateApplicationSpoolerRunTimestamp() error {
|
||||
return v.updateSpoolerRunSequence(applicationTable)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestApplicationFailedEvent(sequence uint64) (*repository.FailedEvent, error) {
|
||||
return v.latestFailedEvent(applicationTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedApplicationFailedEvent(failedEvent *repository.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
||||
|
||||
func (v *View) ApplicationByClientID(_ context.Context, clientID string) (*model.ApplicationView, error) {
|
||||
return view.ApplicationByOIDCClientID(v.Db, applicationTable, clientID)
|
||||
}
|
||||
|
||||
func (v *View) AppIDsFromProjectByClientID(ctx context.Context, clientID string) ([]string, error) {
|
||||
app, err := v.ApplicationByClientID(ctx, clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := &proj_model.ApplicationSearchRequest{
|
||||
Queries: []*proj_model.ApplicationSearchQuery{
|
||||
{
|
||||
Key: proj_model.AppSearchKeyProjectID,
|
||||
Method: domain.SearchMethodEquals,
|
||||
Value: app.ProjectID,
|
||||
},
|
||||
},
|
||||
}
|
||||
apps, _, err := view.SearchApplications(v.Db, applicationTable, req)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowPreconditionFailed(err, "VIEW-Gd24q", "cannot find applications")
|
||||
}
|
||||
ids := make([]string, 0, len(apps))
|
||||
for _, app := range apps {
|
||||
if !app.IsOIDC {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, app.OIDCClientID)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (v *View) AppIDsFromProjectID(ctx context.Context, projectID string) ([]string, error) {
|
||||
req := &proj_model.ApplicationSearchRequest{
|
||||
Queries: []*proj_model.ApplicationSearchQuery{
|
||||
{
|
||||
Key: proj_model.AppSearchKeyProjectID,
|
||||
Method: domain.SearchMethodEquals,
|
||||
Value: projectID,
|
||||
},
|
||||
},
|
||||
}
|
||||
apps, _, err := view.SearchApplications(v.Db, applicationTable, req)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowPreconditionFailed(err, "VIEW-Gd24q", "cannot find applications")
|
||||
}
|
||||
ids := make([]string, 0, len(apps))
|
||||
for _, app := range apps {
|
||||
if !app.IsOIDC {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, app.OIDCClientID)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
)
|
||||
|
||||
type View struct {
|
||||
@ -14,9 +15,10 @@ type View struct {
|
||||
keyAlgorithm crypto.EncryptionAlgorithm
|
||||
idGenerator id.Generator
|
||||
prefixAvatarURL string
|
||||
query *query.Queries
|
||||
}
|
||||
|
||||
func StartView(sqlClient *sql.DB, keyAlgorithm crypto.EncryptionAlgorithm, idGenerator id.Generator, prefixAvatarURL string) (*View, error) {
|
||||
func StartView(sqlClient *sql.DB, keyAlgorithm crypto.EncryptionAlgorithm, queries *query.Queries, idGenerator id.Generator, prefixAvatarURL string) (*View, error) {
|
||||
gorm, err := gorm.Open("postgres", sqlClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -26,6 +28,7 @@ func StartView(sqlClient *sql.DB, keyAlgorithm crypto.EncryptionAlgorithm, idGen
|
||||
keyAlgorithm: keyAlgorithm,
|
||||
idGenerator: idGenerator,
|
||||
prefixAvatarURL: prefixAvatarURL,
|
||||
query: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ func (repo *TokenVerifierRepo) TokenByID(ctx context.Context, tokenID, userID st
|
||||
return model.TokenViewToModel(token), nil
|
||||
}
|
||||
|
||||
func (repo *TokenVerifierRepo) VerifyAccessToken(ctx context.Context, tokenString, verifierClientID string) (userID string, agentID string, clientID, prefLang, resourceOwner string, err error) {
|
||||
func (repo *TokenVerifierRepo) VerifyAccessToken(ctx context.Context, tokenString, verifierClientID, projectID string) (userID string, agentID string, clientID, prefLang, resourceOwner string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
tokenData, err := base64.RawURLEncoding.DecodeString(tokenString)
|
||||
@ -90,10 +90,6 @@ func (repo *TokenVerifierRepo) VerifyAccessToken(ctx context.Context, tokenStrin
|
||||
return "", "", "", "", "", caos_errs.ThrowUnauthenticated(err, "APP-k9KS0", "invalid token")
|
||||
}
|
||||
|
||||
projectID, _, err := repo.ProjectIDAndOriginsByClientID(ctx, verifierClientID)
|
||||
if err != nil {
|
||||
return "", "", "", "", "", caos_errs.ThrowUnauthenticated(err, "APP-5M9so", "invalid token")
|
||||
}
|
||||
for _, aud := range token.Audience {
|
||||
if verifierClientID == aud || projectID == aud {
|
||||
return token.UserID, token.UserAgentID, token.ApplicationID, token.PreferredLanguage, token.ResourceOwner, nil
|
||||
@ -107,7 +103,7 @@ func (repo *TokenVerifierRepo) ProjectIDAndOriginsByClientID(ctx context.Context
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return app.ProjectID, app.OriginAllowList, nil
|
||||
return app.ProjectID, app.OIDCConfig.AllowedOrigins, nil
|
||||
}
|
||||
|
||||
func (repo *TokenVerifierRepo) CheckOrgFeatures(ctx context.Context, orgID string, requiredFeatures ...string) error {
|
||||
@ -237,19 +233,24 @@ func MissingFeatureErr(feature string) error {
|
||||
return caos_errs.ThrowPermissionDeniedf(nil, "AUTH-Dvgsf", "missing feature %v", feature)
|
||||
}
|
||||
|
||||
func (repo *TokenVerifierRepo) VerifierClientID(ctx context.Context, appName string) (_ string, err error) {
|
||||
func (repo *TokenVerifierRepo) VerifierClientID(ctx context.Context, appName string) (clientID, projectID string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
iam, err := repo.getIAMByID(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
app, err := repo.View.ApplicationByProjecIDAndAppName(ctx, iam.IAMProjectID, appName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
return app.OIDCClientID, nil
|
||||
if app.OIDCConfig != nil {
|
||||
clientID = app.OIDCConfig.ClientID
|
||||
} else if app.APIConfig != nil {
|
||||
clientID = app.APIConfig.ClientID
|
||||
}
|
||||
return clientID, app.ProjectID, nil
|
||||
}
|
||||
|
||||
func (r *TokenVerifierRepo) getUserEvents(ctx context.Context, userID string, sequence uint64) ([]*models.Event, error) {
|
||||
|
@ -1,114 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/query"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
|
||||
es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
|
||||
"github.com/caos/zitadel/internal/project/repository/view"
|
||||
view_model "github.com/caos/zitadel/internal/project/repository/view/model"
|
||||
)
|
||||
|
||||
const (
|
||||
applicationTable = "authz.applications"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
handler
|
||||
subscription *v1.Subscription
|
||||
}
|
||||
|
||||
func newApplication(handler handler) *Application {
|
||||
h := &Application{
|
||||
handler: handler,
|
||||
}
|
||||
|
||||
h.subscribe()
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (k *Application) subscribe() {
|
||||
k.subscription = k.es.Subscribe(k.AggregateTypes()...)
|
||||
go func() {
|
||||
for event := range k.subscription.Events {
|
||||
query.ReduceEvent(k, event)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (a *Application) ViewModel() string {
|
||||
return applicationTable
|
||||
}
|
||||
|
||||
func (p *Application) Subscription() *v1.Subscription {
|
||||
return p.subscription
|
||||
}
|
||||
|
||||
func (a *Application) AggregateTypes() []models.AggregateType {
|
||||
return []models.AggregateType{es_model.ProjectAggregate}
|
||||
}
|
||||
|
||||
func (a *Application) CurrentSequence() (uint64, error) {
|
||||
sequence, err := a.view.GetLatestApplicationSequence()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return sequence.CurrentSequence, nil
|
||||
}
|
||||
|
||||
func (a *Application) EventQuery() (*models.SearchQuery, error) {
|
||||
sequence, err := a.view.GetLatestApplicationSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return view.ProjectQuery(sequence.CurrentSequence), nil
|
||||
}
|
||||
|
||||
func (a *Application) Reduce(event *models.Event) (err error) {
|
||||
app := new(view_model.ApplicationView)
|
||||
switch event.Type {
|
||||
case es_model.ApplicationAdded:
|
||||
app.AppendEvent(event)
|
||||
case es_model.ApplicationChanged,
|
||||
es_model.OIDCConfigAdded,
|
||||
es_model.OIDCConfigChanged,
|
||||
es_model.APIConfigAdded,
|
||||
es_model.APIConfigChanged,
|
||||
es_model.ApplicationDeactivated,
|
||||
es_model.ApplicationReactivated:
|
||||
err := app.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app, err = a.view.ApplicationByID(event.AggregateID, app.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app.AppendEvent(event)
|
||||
case es_model.ApplicationRemoved:
|
||||
err := app.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.view.DeleteApplication(app.ID, event)
|
||||
default:
|
||||
return a.view.ProcessedApplicationSequence(event)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.view.PutApplication(app, event)
|
||||
}
|
||||
|
||||
func (a *Application) OnError(event *models.Event, spoolerError error) error {
|
||||
logging.LogWithFields("SPOOL-sjZw", "id", event.AggregateID).WithError(spoolerError).Warn("something went wrong in project app handler")
|
||||
return spooler.HandleError(event, spoolerError, a.view.GetLatestApplicationFailedEvent, a.view.ProcessedApplicationFailedEvent, a.view.ProcessedApplicationSequence, a.errorCountUntilSkip)
|
||||
}
|
||||
|
||||
func (a *Application) OnSuccess() error {
|
||||
return spooler.HandleSuccess(a.view.UpdateApplicationSpoolerRunTimestamp)
|
||||
}
|
@ -36,8 +36,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
|
||||
systemDefaults.IamID),
|
||||
newUserMembership(
|
||||
handler{view, bulkLimit, configs.cycleDuration("UserMemberships"), errorCount, es}),
|
||||
newApplication(
|
||||
handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount, es}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
v1 "github.com/caos/zitadel/internal/eventstore/v1"
|
||||
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
|
||||
@ -43,7 +43,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, qu
|
||||
}
|
||||
|
||||
idGenerator := id.SonyFlakeGenerator
|
||||
view, err := authz_view.StartView(sqlClient, idGenerator)
|
||||
view, err := authz_view.StartView(sqlClient, idGenerator, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -4,69 +4,45 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
"github.com/caos/zitadel/internal/project/repository/view"
|
||||
"github.com/caos/zitadel/internal/project/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
"github.com/caos/zitadel/internal/view/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
applicationTable = "authz.applications"
|
||||
)
|
||||
|
||||
func (v *View) ApplicationByID(projectID, appID string) (*model.ApplicationView, error) {
|
||||
return view.ApplicationByID(v.Db, applicationTable, projectID, appID)
|
||||
func (v *View) ApplicationByOIDCClientID(clientID string) (*query.App, error) {
|
||||
return v.Query.AppByOIDCClientID(context.TODO(), clientID)
|
||||
}
|
||||
|
||||
func (v *View) ApplicationByOIDCClientID(clientID string) (*model.ApplicationView, error) {
|
||||
return view.ApplicationByOIDCClientID(v.Db, applicationTable, clientID)
|
||||
}
|
||||
|
||||
func (v *View) ApplicationByProjecIDAndAppName(ctx context.Context, projectID, appName string) (_ *model.ApplicationView, err error) {
|
||||
func (v *View) ApplicationByProjecIDAndAppName(ctx context.Context, projectID, appName string) (_ *query.App, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
return view.ApplicationByProjectIDAndAppName(v.Db, applicationTable, projectID, appName)
|
||||
}
|
||||
|
||||
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, uint64, error) {
|
||||
return view.SearchApplications(v.Db, applicationTable, request)
|
||||
}
|
||||
|
||||
func (v *View) PutApplication(project *model.ApplicationView, event *models.Event) error {
|
||||
err := view.PutApplication(v.Db, applicationTable, project)
|
||||
nameQuery, err := query.NewAppNameSearchQuery(query.TextEquals, appName)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
return v.ProcessedApplicationSequence(event)
|
||||
}
|
||||
|
||||
func (v *View) DeleteApplication(appID string, event *models.Event) error {
|
||||
err := view.DeleteApplication(v.Db, applicationTable, appID)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
projectQuery, err := query.NewAppProjectIDSearchQuery(projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v.ProcessedApplicationSequence(event)
|
||||
|
||||
queries := &query.AppSearchQueries{
|
||||
Queries: []query.SearchQuery{
|
||||
nameQuery,
|
||||
projectQuery,
|
||||
},
|
||||
}
|
||||
|
||||
apps, err := v.Query.SearchApps(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(apps.Apps) != 1 {
|
||||
return nil, errors.ThrowNotFound(nil, "VIEW-svLQq", "app not found")
|
||||
}
|
||||
|
||||
return apps.Apps[0], nil
|
||||
}
|
||||
|
||||
func (v *View) GetLatestApplicationSequence() (*repository.CurrentSequence, error) {
|
||||
return v.latestSequence(applicationTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedApplicationSequence(event *models.Event) error {
|
||||
return v.saveCurrentSequence(applicationTable, event)
|
||||
}
|
||||
|
||||
func (v *View) UpdateApplicationSpoolerRunTimestamp() error {
|
||||
return v.updateSpoolerRunSequence(applicationTable)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestApplicationFailedEvent(sequence uint64) (*repository.FailedEvent, error) {
|
||||
return v.latestFailedEvent(applicationTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedApplicationFailedEvent(failedEvent *repository.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
func (v *View) SearchApplications(request *query.AppSearchQueries) (*query.Apps, error) {
|
||||
return v.Query.SearchApps(context.TODO(), request)
|
||||
}
|
||||
|
@ -2,17 +2,20 @@ package view
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
type View struct {
|
||||
Db *gorm.DB
|
||||
Query *query.Queries
|
||||
idGenerator id.Generator
|
||||
}
|
||||
|
||||
func StartView(sqlClient *sql.DB, idGenerator id.Generator) (*View, error) {
|
||||
func StartView(sqlClient *sql.DB, idGenerator id.Generator, queries *query.Queries) (*View, error) {
|
||||
gorm, err := gorm.Open("postgres", sqlClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -20,6 +23,7 @@ func StartView(sqlClient *sql.DB, idGenerator id.Generator) (*View, error) {
|
||||
return &View{
|
||||
Db: gorm,
|
||||
idGenerator: idGenerator,
|
||||
Query: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -181,16 +181,38 @@ func GetOIDCCompliance(version OIDCVersion, appType OIDCApplicationType, grantTy
|
||||
case OIDCVersionV1:
|
||||
return GetOIDCV1Compliance(appType, grantTypes, authMethod, redirectUris)
|
||||
}
|
||||
return nil
|
||||
return &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.UnsupportedVersion"},
|
||||
}
|
||||
}
|
||||
|
||||
func GetOIDCV1Compliance(appType OIDCApplicationType, grantTypes []OIDCGrantType, authMethod OIDCAuthMethodType, redirectUris []string) *Compliance {
|
||||
compliance := &Compliance{NoneCompliant: false}
|
||||
if redirectUris == nil || len(redirectUris) == 0 {
|
||||
|
||||
checkGrantTypesCombination(compliance, grantTypes)
|
||||
checkRedirectURIs(compliance, grantTypes, appType, redirectUris)
|
||||
checkApplicaitonType(compliance, appType, authMethod)
|
||||
|
||||
if compliance.NoneCompliant {
|
||||
compliance.Problems = append([]string{"Application.OIDC.V1.NotCompliant"}, compliance.Problems...)
|
||||
}
|
||||
return compliance
|
||||
}
|
||||
|
||||
func checkGrantTypesCombination(compliance *Compliance, grantTypes []OIDCGrantType) {
|
||||
if containsOIDCGrantType(grantTypes, OIDCGrantTypeRefreshToken) && !containsOIDCGrantType(grantTypes, OIDCGrantTypeAuthorizationCode) {
|
||||
compliance.NoneCompliant = true
|
||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.GrantType.Refresh.NoAuthCode")
|
||||
}
|
||||
}
|
||||
|
||||
func checkRedirectURIs(compliance *Compliance, grantTypes []OIDCGrantType, appType OIDCApplicationType, redirectUris []string) {
|
||||
if len(redirectUris) == 0 {
|
||||
compliance.NoneCompliant = true
|
||||
compliance.Problems = append([]string{"Application.OIDC.V1.NoRedirectUris"}, compliance.Problems...)
|
||||
}
|
||||
CheckGrantTypes(compliance, grantTypes)
|
||||
|
||||
if containsOIDCGrantType(grantTypes, OIDCGrantTypeImplicit) && containsOIDCGrantType(grantTypes, OIDCGrantTypeAuthorizationCode) {
|
||||
CheckRedirectUrisImplicitAndCode(compliance, appType, redirectUris)
|
||||
} else {
|
||||
@ -201,7 +223,9 @@ func GetOIDCV1Compliance(appType OIDCApplicationType, grantTypes []OIDCGrantType
|
||||
CheckRedirectUrisCode(compliance, appType, redirectUris)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkApplicaitonType(compliance *Compliance, appType OIDCApplicationType, authMethod OIDCAuthMethodType) {
|
||||
switch appType {
|
||||
case OIDCApplicationTypeNative:
|
||||
GetOIDCV1NativeApplicationCompliance(compliance, authMethod)
|
||||
@ -211,14 +235,6 @@ func GetOIDCV1Compliance(appType OIDCApplicationType, grantTypes []OIDCGrantType
|
||||
if compliance.NoneCompliant {
|
||||
compliance.Problems = append([]string{"Application.OIDC.V1.NotCompliant"}, compliance.Problems...)
|
||||
}
|
||||
return compliance
|
||||
}
|
||||
|
||||
func CheckGrantTypes(compliance *Compliance, grantTypes []OIDCGrantType) {
|
||||
if containsOIDCGrantType(grantTypes, OIDCGrantTypeRefreshToken) && !containsOIDCGrantType(grantTypes, OIDCGrantTypeAuthorizationCode) {
|
||||
compliance.NoneCompliant = true
|
||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.GrantType.Refresh.NoAuthCode")
|
||||
}
|
||||
}
|
||||
|
||||
func GetOIDCV1NativeApplicationCompliance(compliance *Compliance, authMethod OIDCAuthMethodType) {
|
||||
@ -345,3 +361,22 @@ func isHTTPLoopbackLocalhost(uri string) bool {
|
||||
strings.HasPrefix(uri, httpLoopbackV6LongWithoutPort) ||
|
||||
strings.HasPrefix(uri, httpLoopbackV6LongWithPort)
|
||||
}
|
||||
|
||||
func OIDCOriginAllowList(redirectURIs, additionalOrigins []string) ([]string, error) {
|
||||
allowList := make([]string, 0)
|
||||
for _, redirect := range redirectURIs {
|
||||
origin, err := http_util.GetOriginFromURLString(redirect)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !http_util.IsOriginAllowed(allowList, origin) {
|
||||
allowList = append(allowList, origin)
|
||||
}
|
||||
}
|
||||
for _, origin := range additionalOrigins {
|
||||
if !http_util.IsOriginAllowed(allowList, origin) {
|
||||
allowList = append(allowList, origin)
|
||||
}
|
||||
}
|
||||
return allowList, nil
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -185,3 +187,489 @@ func TestApplicationValid(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOIDCV1Compliance(t *testing.T) {
|
||||
type args struct {
|
||||
appType OIDCApplicationType
|
||||
grantTypes []OIDCGrantType
|
||||
authMethod OIDCAuthMethodType
|
||||
redirectUris []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "none compliant",
|
||||
args: args{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := GetOIDCV1Compliance(tt.args.appType, tt.args.grantTypes, tt.args.authMethod, tt.args.redirectUris)
|
||||
if !got.NoneCompliant {
|
||||
t.Error("compliance should be none compliant")
|
||||
}
|
||||
if len(got.Problems) == 0 || got.Problems[0] != "Application.OIDC.V1.NotCompliant" {
|
||||
t.Errorf("first entry of problems should be \"Application.OIDC.V1.NotCompliant\" but got %v", got.Problems)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_checkGrantTypesCombination(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want *Compliance
|
||||
grantTypes []OIDCGrantType
|
||||
}{
|
||||
{
|
||||
name: "implicit",
|
||||
want: new(Compliance),
|
||||
grantTypes: []OIDCGrantType{OIDCGrantTypeImplicit},
|
||||
},
|
||||
{
|
||||
name: "refresh token and implicit",
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.V1.GrantType.Refresh.NoAuthCode"},
|
||||
},
|
||||
grantTypes: []OIDCGrantType{OIDCGrantTypeImplicit, OIDCGrantTypeRefreshToken},
|
||||
},
|
||||
{
|
||||
name: "refresh token and authorization code",
|
||||
want: &Compliance{},
|
||||
grantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode, OIDCGrantTypeRefreshToken},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
compliance := new(Compliance)
|
||||
|
||||
checkGrantTypesCombination(compliance, tt.grantTypes)
|
||||
|
||||
if tt.want.NoneCompliant != compliance.NoneCompliant {
|
||||
t.Errorf("NoneCompliant: expected: %v, got %v", tt.want.NoneCompliant, compliance.NoneCompliant)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.want.Problems, compliance.Problems) {
|
||||
t.Errorf("Problems: expected: %v, got %v", tt.want.Problems, compliance.Problems)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_checkRedirectURIs(t *testing.T) {
|
||||
type args struct {
|
||||
grantTypes []OIDCGrantType
|
||||
appType OIDCApplicationType
|
||||
redirectUris []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
want *Compliance
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "no redirect uris",
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{
|
||||
"Application.OIDC.V1.NoRedirectUris",
|
||||
},
|
||||
},
|
||||
args: args{},
|
||||
},
|
||||
{
|
||||
name: "implicit and authorization code",
|
||||
want: &Compliance{
|
||||
NoneCompliant: false,
|
||||
Problems: []string{"Application.OIDC.V1.NotAllCombinationsAreAllowed"},
|
||||
},
|
||||
args: args{
|
||||
redirectUris: []string{"http://redirect.to/me"},
|
||||
grantTypes: []OIDCGrantType{OIDCGrantTypeImplicit, OIDCGrantTypeAuthorizationCode},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only implicit",
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.V1.Implicit.RedirectUris.HttpNotAllowed"},
|
||||
},
|
||||
args: args{
|
||||
redirectUris: []string{"http://redirect.to/me"},
|
||||
grantTypes: []OIDCGrantType{OIDCGrantTypeImplicit},
|
||||
appType: OIDCApplicationTypeUserAgent,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only authorization code",
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb"},
|
||||
},
|
||||
args: args{
|
||||
redirectUris: []string{"http://redirect.to/me"},
|
||||
grantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
|
||||
appType: OIDCApplicationTypeUserAgent,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
compliance := new(Compliance)
|
||||
|
||||
checkRedirectURIs(compliance, tt.args.grantTypes, tt.args.appType, tt.args.redirectUris)
|
||||
|
||||
if tt.want.NoneCompliant != compliance.NoneCompliant {
|
||||
t.Errorf("NoneCompliant: expected: %v, got %v", tt.want.NoneCompliant, compliance.NoneCompliant)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.want.Problems, compliance.Problems) {
|
||||
t.Errorf("Problems: expected: %v, got %v", tt.want.Problems, compliance.Problems)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CheckRedirectUrisImplicitAndCode(t *testing.T) {
|
||||
type args struct {
|
||||
appType OIDCApplicationType
|
||||
redirectUris []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
want *Compliance
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "implicit and code https",
|
||||
want: &Compliance{
|
||||
NoneCompliant: false,
|
||||
Problems: nil,
|
||||
},
|
||||
args: args{
|
||||
redirectUris: []string{"https://redirect.to/me"},
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: "custom protocol, not native",
|
||||
// want: &Compliance{
|
||||
// NoneCompliant: true,
|
||||
// Problems: []string{"Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed"},
|
||||
// },
|
||||
// args: args{
|
||||
// redirectUris: []string{"protocol://redirect.to/me"},
|
||||
// appType: OIDCApplicationTypeWeb,
|
||||
// },
|
||||
// },
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
compliance := new(Compliance)
|
||||
|
||||
CheckRedirectUrisImplicitAndCode(compliance, tt.args.appType, tt.args.redirectUris)
|
||||
|
||||
if tt.want.NoneCompliant != compliance.NoneCompliant {
|
||||
t.Errorf("NoneCompliant: expected: %v, got %v", tt.want.NoneCompliant, compliance.NoneCompliant)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.want.Problems, compliance.Problems) {
|
||||
t.Errorf("Problems: expected: %v, got %v", tt.want.Problems, compliance.Problems)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRedirectUrisImplicitAndCode(t *testing.T) {
|
||||
type args struct {
|
||||
appType OIDCApplicationType
|
||||
redirectUris []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *Compliance
|
||||
}{
|
||||
{
|
||||
name: "only https",
|
||||
args: args{},
|
||||
want: &Compliance{},
|
||||
},
|
||||
{
|
||||
name: "custom protocol not native app",
|
||||
args: args{
|
||||
appType: OIDCApplicationTypeWeb,
|
||||
redirectUris: []string{"custom://nirvana.com"},
|
||||
},
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "http localhost user agent app",
|
||||
args: args{
|
||||
appType: OIDCApplicationTypeUserAgent,
|
||||
redirectUris: []string{"http://localhost:9009"},
|
||||
},
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "http, not only localhost native app",
|
||||
args: args{
|
||||
appType: OIDCApplicationTypeNative,
|
||||
redirectUris: []string{"http://nirvana.com", "http://localhost:9009"},
|
||||
},
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not allowed combination",
|
||||
args: args{
|
||||
appType: OIDCApplicationTypeNative,
|
||||
redirectUris: []string{"https://nirvana.com", "cutom://nirvana.com"},
|
||||
},
|
||||
want: &Compliance{
|
||||
NoneCompliant: false,
|
||||
Problems: []string{"Application.OIDC.V1.NotAllCombinationsAreAllowed"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := new(Compliance)
|
||||
CheckRedirectUrisImplicitAndCode(got, tt.args.appType, tt.args.redirectUris)
|
||||
|
||||
if tt.want.NoneCompliant != got.NoneCompliant {
|
||||
t.Errorf("NoneCompliant: expected: %v, got %v", tt.want.NoneCompliant, got.NoneCompliant)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.want.Problems, got.Problems) {
|
||||
t.Errorf("Problems: expected: %v, got %v", tt.want.Problems, got.Problems)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRedirectUrisImplicit(t *testing.T) {
|
||||
type args struct {
|
||||
appType OIDCApplicationType
|
||||
redirectUris []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *Compliance
|
||||
}{
|
||||
{
|
||||
name: "only https",
|
||||
args: args{},
|
||||
want: &Compliance{},
|
||||
},
|
||||
{
|
||||
name: "custom protocol",
|
||||
args: args{
|
||||
redirectUris: []string{"custom://nirvana.com"},
|
||||
},
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only http protocol, app type native, not only localhost",
|
||||
args: args{
|
||||
redirectUris: []string{"http://nirvana.com"},
|
||||
appType: OIDCApplicationTypeNative,
|
||||
},
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only http protocol, app type native, only localhost",
|
||||
args: args{
|
||||
redirectUris: []string{"http://localhost:8080"},
|
||||
appType: OIDCApplicationTypeNative,
|
||||
},
|
||||
want: &Compliance{
|
||||
NoneCompliant: false,
|
||||
Problems: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only http protocol, app type web",
|
||||
args: args{
|
||||
redirectUris: []string{"http://nirvana.com"},
|
||||
appType: OIDCApplicationTypeWeb,
|
||||
},
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.V1.Implicit.RedirectUris.HttpNotAllowed"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := new(Compliance)
|
||||
CheckRedirectUrisImplicit(got, tt.args.appType, tt.args.redirectUris)
|
||||
|
||||
if tt.want.NoneCompliant != got.NoneCompliant {
|
||||
t.Errorf("NoneCompliant: expected: %v, got %v", tt.want.NoneCompliant, got.NoneCompliant)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.want.Problems, got.Problems) {
|
||||
t.Errorf("Problems: expected: %v, got %v", tt.want.Problems, got.Problems)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRedirectUrisCode(t *testing.T) {
|
||||
type args struct {
|
||||
appType OIDCApplicationType
|
||||
redirectUris []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *Compliance
|
||||
}{
|
||||
{
|
||||
name: "only https",
|
||||
args: args{},
|
||||
want: &Compliance{},
|
||||
},
|
||||
{
|
||||
name: "custom prefix, app type web",
|
||||
args: args{
|
||||
redirectUris: []string{"custom://nirvana.com"},
|
||||
appType: OIDCApplicationTypeWeb,
|
||||
},
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.V1.Code.RedirectUris.CustomOnlyForNative"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only http protocol, app type user agent",
|
||||
args: args{
|
||||
redirectUris: []string{"http://nirvana.com"},
|
||||
appType: OIDCApplicationTypeUserAgent,
|
||||
},
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only http protocol, app type native, only localhost",
|
||||
args: args{
|
||||
redirectUris: []string{"http://localhost:8080", "http://nirvana.com:8080"},
|
||||
appType: OIDCApplicationTypeNative,
|
||||
},
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "custom protocol, not native",
|
||||
args: args{
|
||||
redirectUris: []string{"custom://nirvana.com"},
|
||||
appType: OIDCApplicationTypeWeb,
|
||||
},
|
||||
want: &Compliance{
|
||||
NoneCompliant: true,
|
||||
Problems: []string{"Application.OIDC.V1.Code.RedirectUris.CustomOnlyForNative"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := new(Compliance)
|
||||
CheckRedirectUrisCode(got, tt.args.appType, tt.args.redirectUris)
|
||||
|
||||
if tt.want.NoneCompliant != got.NoneCompliant {
|
||||
t.Errorf("NoneCompliant: expected: %v, got %v", tt.want.NoneCompliant, got.NoneCompliant)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.want.Problems, got.Problems) {
|
||||
t.Errorf("Problems: expected: %v, got %v", tt.want.Problems, got.Problems)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOIDCOriginAllowList(t *testing.T) {
|
||||
type args struct {
|
||||
redirectUris []string
|
||||
additionalOrigins []string
|
||||
}
|
||||
type want struct {
|
||||
allowed []string
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "no uris, no origins",
|
||||
args: args{},
|
||||
want: want{
|
||||
allowed: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "redirects invalid schema",
|
||||
args: args{
|
||||
redirectUris: []string{"https:// localhost:8080"},
|
||||
},
|
||||
want: want{
|
||||
allowed: nil,
|
||||
err: func(e error) bool {
|
||||
return strings.HasPrefix(e.Error(), "invalid chavalid character")
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "redirects additional",
|
||||
args: args{
|
||||
redirectUris: []string{"https://localhost:8080"},
|
||||
},
|
||||
want: want{
|
||||
allowed: []string{"https://localhost:8080"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "additional origin",
|
||||
args: args{
|
||||
additionalOrigins: []string{"https://localhost:8080"},
|
||||
},
|
||||
want: want{
|
||||
allowed: []string{"https://localhost:8080"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
allowed, err := OIDCOriginAllowList(tt.args.redirectUris, tt.args.additionalOrigins)
|
||||
|
||||
if tt.want.err == nil && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
} else if tt.want.err == nil && err == nil {
|
||||
//ok
|
||||
} else if tt.want.err(err) {
|
||||
t.Errorf("unexpected err got %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(allowed, tt.want.allowed) {
|
||||
t.Errorf("expected list: %v, got: %v", tt.want.allowed, allowed)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -93,63 +93,6 @@ func (repo *ProjectRepo) ProjectChanges(ctx context.Context, id string, lastSequ
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) ApplicationByID(ctx context.Context, projectID, appID string) (*proj_model.ApplicationView, error) {
|
||||
app, viewErr := repo.View.ApplicationByID(projectID, appID)
|
||||
if viewErr != nil && !caos_errs.IsNotFound(viewErr) {
|
||||
return nil, viewErr
|
||||
}
|
||||
if caos_errs.IsNotFound(viewErr) {
|
||||
app = new(model.ApplicationView)
|
||||
app.ID = appID
|
||||
}
|
||||
|
||||
events, esErr := repo.getProjectEvents(ctx, projectID, app.Sequence)
|
||||
if caos_errs.IsNotFound(viewErr) && len(events) == 0 {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "EVENT-Fshu8", "Errors.Application.NotFound")
|
||||
}
|
||||
|
||||
if esErr != nil {
|
||||
logging.Log("EVENT-SLCo9").WithError(viewErr).Debug("error retrieving new events")
|
||||
return model.ApplicationViewToModel(app), nil
|
||||
}
|
||||
|
||||
viewApp := *app
|
||||
for _, event := range events {
|
||||
err := app.AppendEventIfMyApp(event)
|
||||
if err != nil {
|
||||
return model.ApplicationViewToModel(&viewApp), nil
|
||||
}
|
||||
if app.State == int32(proj_model.AppStateRemoved) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "EVENT-Msl96", "Errors.Application.NotFound")
|
||||
}
|
||||
}
|
||||
return model.ApplicationViewToModel(app), nil
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) SearchApplications(ctx context.Context, request *proj_model.ApplicationSearchRequest) (*proj_model.ApplicationSearchResponse, error) {
|
||||
err := request.EnsureLimit(repo.SearchLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sequence, sequenceErr := repo.View.GetLatestApplicationSequence()
|
||||
logging.Log("EVENT-SKe8s").OnError(sequenceErr).Warn("could not read latest application sequence")
|
||||
apps, count, err := repo.View.SearchApplications(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &proj_model.ApplicationSearchResponse{
|
||||
Offset: request.Offset,
|
||||
Limit: request.Limit,
|
||||
TotalResult: count,
|
||||
Result: model.ApplicationViewsToModel(apps),
|
||||
}
|
||||
if sequenceErr == nil {
|
||||
result.Sequence = sequence.CurrentSequence
|
||||
result.Timestamp = sequence.LastSuccessfulSpoolerRun
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) ApplicationChanges(ctx context.Context, projectID string, appID string, lastSequence uint64, limit uint64, sortAscending bool, retention time.Duration) (*proj_model.ApplicationChanges, error) {
|
||||
changes, err := repo.getApplicationChanges(ctx, projectID, appID, lastSequence, limit, sortAscending, retention)
|
||||
if err != nil {
|
||||
|
@ -1,167 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/query"
|
||||
es_sdk "github.com/caos/zitadel/internal/eventstore/v1/sdk"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
|
||||
proj_view "github.com/caos/zitadel/internal/project/repository/view"
|
||||
view_model "github.com/caos/zitadel/internal/project/repository/view/model"
|
||||
)
|
||||
|
||||
const (
|
||||
applicationTable = "management.applications"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
handler
|
||||
subscription *v1.Subscription
|
||||
}
|
||||
|
||||
func newApplication(
|
||||
handler handler,
|
||||
) *Application {
|
||||
h := &Application{
|
||||
handler: handler,
|
||||
}
|
||||
|
||||
h.subscribe()
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (a *Application) subscribe() {
|
||||
a.subscription = a.es.Subscribe(a.AggregateTypes()...)
|
||||
go func() {
|
||||
for event := range a.subscription.Events {
|
||||
query.ReduceEvent(a, event)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (a *Application) ViewModel() string {
|
||||
return applicationTable
|
||||
}
|
||||
|
||||
func (a *Application) Subscription() *v1.Subscription {
|
||||
return a.subscription
|
||||
}
|
||||
|
||||
func (_ *Application) AggregateTypes() []models.AggregateType {
|
||||
return []models.AggregateType{es_model.ProjectAggregate}
|
||||
}
|
||||
|
||||
func (a *Application) CurrentSequence() (uint64, error) {
|
||||
sequence, err := a.view.GetLatestApplicationSequence()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return sequence.CurrentSequence, nil
|
||||
}
|
||||
|
||||
func (a *Application) EventQuery() (*models.SearchQuery, error) {
|
||||
sequence, err := a.view.GetLatestApplicationSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proj_view.ProjectQuery(sequence.CurrentSequence), nil
|
||||
}
|
||||
|
||||
func (a *Application) Reduce(event *models.Event) (err error) {
|
||||
app := new(view_model.ApplicationView)
|
||||
switch event.Type {
|
||||
case es_model.ApplicationAdded:
|
||||
project, err := a.getProjectByID(context.Background(), event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app.ProjectRoleCheck = project.ProjectRoleCheck
|
||||
app.HasProjectCheck = project.HasProjectCheck
|
||||
app.ProjectRoleAssertion = project.ProjectRoleAssertion
|
||||
app.PrivateLabelingSetting = project.PrivateLabelingSetting
|
||||
|
||||
err = app.AppendEvent(event)
|
||||
case es_model.ApplicationChanged,
|
||||
es_model.OIDCConfigAdded,
|
||||
es_model.OIDCConfigChanged,
|
||||
es_model.APIConfigAdded,
|
||||
es_model.APIConfigChanged,
|
||||
es_model.ApplicationDeactivated,
|
||||
es_model.ApplicationReactivated:
|
||||
err = app.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app, err = a.view.ApplicationByID(event.AggregateID, app.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = app.AppendEvent(event)
|
||||
case es_model.ApplicationRemoved:
|
||||
err = app.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.view.DeleteApplication(app.ID, event)
|
||||
case es_model.ProjectChanged:
|
||||
apps, err := a.view.ApplicationsByProjectID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(apps) == 0 {
|
||||
return a.view.ProcessedApplicationSequence(event)
|
||||
}
|
||||
for _, app := range apps {
|
||||
if err := app.AppendEvent(event); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return a.view.PutApplications(apps, event)
|
||||
case es_model.ProjectRemoved:
|
||||
return a.view.DeleteApplicationsByProjectID(event.AggregateID)
|
||||
default:
|
||||
return a.view.ProcessedApplicationSequence(event)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.view.PutApplication(app, event)
|
||||
}
|
||||
|
||||
func (a *Application) OnError(event *models.Event, spoolerError error) error {
|
||||
logging.LogWithFields("SPOOL-ls9ew", "id", event.AggregateID).WithError(spoolerError).Warn("something went wrong in project app handler")
|
||||
return spooler.HandleError(event, spoolerError, a.view.GetLatestApplicationFailedEvent, a.view.ProcessedApplicationFailedEvent, a.view.ProcessedApplicationSequence, a.errorCountUntilSkip)
|
||||
}
|
||||
|
||||
func (a *Application) OnSuccess() error {
|
||||
return spooler.HandleSuccess(a.view.UpdateApplicationSpoolerRunTimestamp)
|
||||
}
|
||||
|
||||
func (a *Application) getProjectByID(ctx context.Context, projID string) (*proj_model.Project, error) {
|
||||
query, err := proj_view.ProjectByIDQuery(projID, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
esProject := &es_model.Project{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: projID,
|
||||
},
|
||||
}
|
||||
err = es_sdk.Filter(ctx, a.Eventstore().FilterEvents, esProject.AppendEvents, query)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
if esProject.Sequence == 0 {
|
||||
return nil, errors.ThrowNotFound(nil, "EVENT-ADvfs", "Errors.Project.NotFound")
|
||||
}
|
||||
|
||||
return es_model.ProjectToModel(esProject), nil
|
||||
}
|
@ -35,7 +35,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
|
||||
return []query.Handler{
|
||||
newProjectMember(handler{view, bulkLimit, configs.cycleDuration("ProjectMember"), errorCount, es}),
|
||||
newProjectGrantMember(handler{view, bulkLimit, configs.cycleDuration("ProjectGrantMember"), errorCount, es}),
|
||||
newApplication(handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount, es}),
|
||||
newUser(handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es},
|
||||
defaults.IamID),
|
||||
newUserGrant(handler{view, bulkLimit, configs.cycleDuration("UserGrant"), errorCount, es}),
|
||||
|
@ -1,74 +0,0 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
"github.com/caos/zitadel/internal/project/repository/view"
|
||||
"github.com/caos/zitadel/internal/project/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/view/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
applicationTable = "management.applications"
|
||||
)
|
||||
|
||||
func (v *View) ApplicationByID(projectID, appID string) (*model.ApplicationView, error) {
|
||||
return view.ApplicationByID(v.Db, applicationTable, projectID, appID)
|
||||
}
|
||||
|
||||
func (v *View) ApplicationsByProjectID(projectID string) ([]*model.ApplicationView, error) {
|
||||
return view.ApplicationsByProjectID(v.Db, applicationTable, projectID)
|
||||
}
|
||||
|
||||
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, uint64, error) {
|
||||
return view.SearchApplications(v.Db, applicationTable, request)
|
||||
}
|
||||
|
||||
func (v *View) PutApplication(app *model.ApplicationView, event *models.Event) error {
|
||||
err := view.PutApplication(v.Db, applicationTable, app)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedApplicationSequence(event)
|
||||
}
|
||||
|
||||
func (v *View) PutApplications(apps []*model.ApplicationView, event *models.Event) error {
|
||||
err := view.PutApplications(v.Db, applicationTable, apps...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedApplicationSequence(event)
|
||||
}
|
||||
|
||||
func (v *View) DeleteApplication(appID string, event *models.Event) error {
|
||||
err := view.DeleteApplication(v.Db, applicationTable, appID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedApplicationSequence(event)
|
||||
}
|
||||
|
||||
func (v *View) DeleteApplicationsByProjectID(projectID string) error {
|
||||
return view.DeleteApplicationsByProjectID(v.Db, applicationTable, projectID)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestApplicationSequence() (*repository.CurrentSequence, error) {
|
||||
return v.latestSequence(applicationTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedApplicationSequence(event *models.Event) error {
|
||||
return v.saveCurrentSequence(applicationTable, event)
|
||||
}
|
||||
|
||||
func (v *View) UpdateApplicationSpoolerRunTimestamp() error {
|
||||
return v.updateSpoolerRunSequence(applicationTable)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestApplicationFailedEvent(sequence uint64) (*repository.FailedEvent, error) {
|
||||
return v.latestFailedEvent(applicationTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedApplicationFailedEvent(failedEvent *repository.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
@ -17,8 +17,6 @@ type ProjectRepository interface {
|
||||
|
||||
ProjectChanges(ctx context.Context, id string, lastSequence uint64, limit uint64, sortAscending bool, retention time.Duration) (*model.ProjectChanges, error)
|
||||
|
||||
ApplicationByID(ctx context.Context, projectID, appID string) (*model.ApplicationView, error)
|
||||
SearchApplications(ctx context.Context, request *model.ApplicationSearchRequest) (*model.ApplicationSearchResponse, error)
|
||||
ApplicationChanges(ctx context.Context, projectID string, appID string, lastSequence uint64, limit uint64, sortAscending bool, retention time.Duration) (*model.ApplicationChanges, error)
|
||||
SearchClientKeys(ctx context.Context, request *key_model.AuthNKeySearchRequest) (*key_model.AuthNKeySearchResponse, error)
|
||||
GetClientKey(ctx context.Context, projectID, applicationID, keyID string) (*key_model.AuthNKeyView, error)
|
||||
|
@ -1,89 +0,0 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
"github.com/caos/zitadel/internal/project/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/view/repository"
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
func ApplicationByID(db *gorm.DB, table, projectID, appID string) (*model.ApplicationView, error) {
|
||||
app := new(model.ApplicationView)
|
||||
projectIDQuery := &model.ApplicationSearchQuery{Key: proj_model.AppSearchKeyProjectID, Value: projectID, Method: domain.SearchMethodEquals}
|
||||
appIDQuery := &model.ApplicationSearchQuery{Key: proj_model.AppSearchKeyAppID, Value: appID, Method: domain.SearchMethodEquals}
|
||||
query := repository.PrepareGetByQuery(table, projectIDQuery, appIDQuery)
|
||||
err := query(db, app)
|
||||
if caos_errs.IsNotFound(err) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "VIEW-DGdfx", "Errors.Application.NotFound")
|
||||
}
|
||||
return app, err
|
||||
}
|
||||
|
||||
func ApplicationsByProjectID(db *gorm.DB, table, projectID string) ([]*model.ApplicationView, error) {
|
||||
applications := make([]*model.ApplicationView, 0)
|
||||
queries := []*proj_model.ApplicationSearchQuery{
|
||||
{Key: proj_model.AppSearchKeyProjectID, Value: projectID, Method: domain.SearchMethodEquals},
|
||||
}
|
||||
query := repository.PrepareSearchQuery(table, model.ApplicationSearchRequest{Queries: queries})
|
||||
_, err := query(db, &applications)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return applications, nil
|
||||
}
|
||||
|
||||
func ApplicationByOIDCClientID(db *gorm.DB, table, clientID string) (*model.ApplicationView, error) {
|
||||
app := new(model.ApplicationView)
|
||||
clientIDQuery := model.ApplicationSearchQuery{Key: proj_model.AppSearchKeyOIDCClientID, Value: clientID, Method: domain.SearchMethodEquals}
|
||||
query := repository.PrepareGetByQuery(table, clientIDQuery)
|
||||
err := query(db, app)
|
||||
if caos_errs.IsNotFound(err) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "VIEW-DG1qh", "Errors.Project.App.NotFound")
|
||||
}
|
||||
return app, err
|
||||
}
|
||||
|
||||
func ApplicationByProjectIDAndAppName(db *gorm.DB, table, projectID, appName string) (*model.ApplicationView, error) {
|
||||
app := new(model.ApplicationView)
|
||||
projectIDQuery := model.ApplicationSearchQuery{Key: proj_model.AppSearchKeyProjectID, Value: projectID, Method: domain.SearchMethodEquals}
|
||||
appNameQuery := model.ApplicationSearchQuery{Key: proj_model.AppSearchKeyName, Value: appName, Method: domain.SearchMethodEquals}
|
||||
query := repository.PrepareGetByQuery(table, projectIDQuery, appNameQuery)
|
||||
err := query(db, app)
|
||||
if caos_errs.IsNotFound(err) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "VIEW-Jqw1z", "Errors.Project.App.NotFound")
|
||||
}
|
||||
return app, err
|
||||
}
|
||||
|
||||
func SearchApplications(db *gorm.DB, table string, req *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, uint64, error) {
|
||||
apps := make([]*model.ApplicationView, 0)
|
||||
query := repository.PrepareSearchQuery(table, model.ApplicationSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
|
||||
count, err := query(db, &apps)
|
||||
return apps, count, err
|
||||
}
|
||||
|
||||
func PutApplication(db *gorm.DB, table string, app *model.ApplicationView) error {
|
||||
save := repository.PrepareSave(table)
|
||||
return save(db, app)
|
||||
}
|
||||
|
||||
func PutApplications(db *gorm.DB, table string, apps ...*model.ApplicationView) error {
|
||||
save := repository.PrepareBulkSave(table)
|
||||
s := make([]interface{}, len(apps))
|
||||
for i, app := range apps {
|
||||
s[i] = app
|
||||
}
|
||||
return save(db, s...)
|
||||
}
|
||||
|
||||
func DeleteApplication(db *gorm.DB, table, appID string) error {
|
||||
delete := repository.PrepareDeleteByKey(table, model.ApplicationSearchKey(proj_model.AppSearchKeyAppID), appID)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteApplicationsByProjectID(db *gorm.DB, table, projectID string) error {
|
||||
delete := repository.PrepareDeleteByKey(table, model.ApplicationSearchKey(proj_model.AppSearchKeyProjectID), projectID)
|
||||
return delete(db)
|
||||
}
|
658
internal/query/app.go
Normal file
658
internal/query/app.go
Normal file
@ -0,0 +1,658 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
errs "errors"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/lib/pq"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/query/projection"
|
||||
)
|
||||
|
||||
type Apps struct {
|
||||
SearchResponse
|
||||
Apps []*App
|
||||
}
|
||||
|
||||
type App struct {
|
||||
ID string
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
ResourceOwner string
|
||||
State domain.AppState
|
||||
Sequence uint64
|
||||
|
||||
ProjectID string
|
||||
Name string
|
||||
|
||||
OIDCConfig *OIDCApp
|
||||
APIConfig *APIApp
|
||||
}
|
||||
|
||||
type OIDCApp struct {
|
||||
RedirectURIs []string
|
||||
ResponseTypes []domain.OIDCResponseType
|
||||
GrantTypes []domain.OIDCGrantType
|
||||
AppType domain.OIDCApplicationType
|
||||
ClientID string
|
||||
AuthMethodType domain.OIDCAuthMethodType
|
||||
PostLogoutRedirectURIs []string
|
||||
Version domain.OIDCVersion
|
||||
ComplianceProblems []string
|
||||
IsDevMode bool
|
||||
AccessTokenType domain.OIDCTokenType
|
||||
AssertAccessTokenRole bool
|
||||
AssertIDTokenRole bool
|
||||
AssertIDTokenUserinfo bool
|
||||
ClockSkew time.Duration
|
||||
AdditionalOrigins []string
|
||||
AllowedOrigins []string
|
||||
}
|
||||
|
||||
type APIApp struct {
|
||||
ClientID string
|
||||
AuthMethodType domain.APIAuthMethodType
|
||||
}
|
||||
|
||||
type AppSearchQueries struct {
|
||||
SearchRequest
|
||||
Queries []SearchQuery
|
||||
}
|
||||
|
||||
func (q *AppSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||
query = q.SearchRequest.toQuery(query)
|
||||
for _, q := range q.Queries {
|
||||
query = q.toQuery(query)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
var (
|
||||
appsTable = table{
|
||||
name: projection.AppProjectionTable,
|
||||
}
|
||||
AppColumnID = Column{
|
||||
name: projection.AppColumnID,
|
||||
table: appsTable,
|
||||
}
|
||||
AppColumnName = Column{
|
||||
name: projection.AppColumnName,
|
||||
table: appsTable,
|
||||
}
|
||||
AppColumnProjectID = Column{
|
||||
name: projection.AppColumnProjectID,
|
||||
table: appsTable,
|
||||
}
|
||||
AppColumnCreationDate = Column{
|
||||
name: projection.AppColumnCreationDate,
|
||||
table: appsTable,
|
||||
}
|
||||
AppColumnChangeDate = Column{
|
||||
name: projection.AppColumnChangeDate,
|
||||
table: appsTable,
|
||||
}
|
||||
AppColumnResourceOwner = Column{
|
||||
name: projection.AppColumnResourceOwner,
|
||||
table: appsTable,
|
||||
}
|
||||
AppColumnState = Column{
|
||||
name: projection.AppColumnState,
|
||||
table: appsTable,
|
||||
}
|
||||
AppColumnSequence = Column{
|
||||
name: projection.AppColumnSequence,
|
||||
table: appsTable,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
appAPIConfigsTable = table{
|
||||
name: projection.AppAPITable,
|
||||
}
|
||||
AppAPIConfigColumnAppID = Column{
|
||||
name: projection.AppAPIConfigColumnAppID,
|
||||
table: appAPIConfigsTable,
|
||||
}
|
||||
AppAPIConfigColumnClientID = Column{
|
||||
name: projection.AppAPIConfigColumnClientID,
|
||||
table: appAPIConfigsTable,
|
||||
}
|
||||
AppAPIConfigColumnAuthMethod = Column{
|
||||
name: projection.AppAPIConfigColumnAuthMethod,
|
||||
table: appAPIConfigsTable,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
appOIDCConfigsTable = table{
|
||||
name: projection.AppOIDCTable,
|
||||
}
|
||||
AppOIDCConfigColumnAppID = Column{
|
||||
name: projection.AppOIDCConfigColumnAppID,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnVersion = Column{
|
||||
name: projection.AppOIDCConfigColumnVersion,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnClientID = Column{
|
||||
name: projection.AppOIDCConfigColumnClientID,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnRedirectUris = Column{
|
||||
name: projection.AppOIDCConfigColumnRedirectUris,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnResponseTypes = Column{
|
||||
name: projection.AppOIDCConfigColumnResponseTypes,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnGrantTypes = Column{
|
||||
name: projection.AppOIDCConfigColumnGrantTypes,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnApplicationType = Column{
|
||||
name: projection.AppOIDCConfigColumnApplicationType,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnAuthMethodType = Column{
|
||||
name: projection.AppOIDCConfigColumnAuthMethodType,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnPostLogoutRedirectUris = Column{
|
||||
name: projection.AppOIDCConfigColumnPostLogoutRedirectUris,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnDevMode = Column{
|
||||
name: projection.AppOIDCConfigColumnDevMode,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnAccessTokenType = Column{
|
||||
name: projection.AppOIDCConfigColumnAccessTokenType,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnAccessTokenRoleAssertion = Column{
|
||||
name: projection.AppOIDCConfigColumnAccessTokenRoleAssertion,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnIDTokenRoleAssertion = Column{
|
||||
name: projection.AppOIDCConfigColumnIDTokenRoleAssertion,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnIDTokenUserinfoAssertion = Column{
|
||||
name: projection.AppOIDCConfigColumnIDTokenUserinfoAssertion,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnClockSkew = Column{
|
||||
name: projection.AppOIDCConfigColumnClockSkew,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnAdditionalOrigins = Column{
|
||||
name: projection.AppOIDCConfigColumnAdditionalOrigins,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
)
|
||||
|
||||
func (q *Queries) AppByProjectAndAppID(ctx context.Context, projectID, appID string) (*App, error) {
|
||||
stmt, scan := prepareAppQuery()
|
||||
query, args, err := stmt.Where(
|
||||
sq.Eq{
|
||||
AppColumnID.identifier(): appID,
|
||||
AppColumnProjectID.identifier(): projectID,
|
||||
},
|
||||
).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-AFDgg", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func (q *Queries) AppByID(ctx context.Context, appID string) (*App, error) {
|
||||
stmt, scan := prepareAppQuery()
|
||||
query, args, err := stmt.Where(
|
||||
sq.Eq{AppColumnID.identifier(): appID},
|
||||
).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-immt9", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func (q *Queries) ProjectIDFromOIDCClientID(ctx context.Context, appID string) (string, error) {
|
||||
stmt, scan := prepareProjectIDByAppQuery()
|
||||
query, args, err := stmt.Where(
|
||||
sq.Eq{AppOIDCConfigColumnClientID.identifier(): appID},
|
||||
).ToSql()
|
||||
if err != nil {
|
||||
return "", errors.ThrowInternal(err, "QUERY-7d92U", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func (q *Queries) ProjectByOIDCClientID(ctx context.Context, id string) (*Project, error) {
|
||||
stmt, scan := prepareProjectByAppQuery()
|
||||
query, args, err := stmt.Where(
|
||||
sq.Eq{AppOIDCConfigColumnClientID.identifier(): id},
|
||||
).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-XhJi4", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func (q *Queries) AppByOIDCClientID(ctx context.Context, clientID string) (*App, error) {
|
||||
stmt, scan := prepareAppQuery()
|
||||
query, args, err := stmt.Where(
|
||||
sq.Eq{
|
||||
AppOIDCConfigColumnClientID.identifier(): clientID,
|
||||
},
|
||||
).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-JgVop", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func (q *Queries) SearchApps(ctx context.Context, queries *AppSearchQueries) (*Apps, error) {
|
||||
query, scan := prepareAppsQuery()
|
||||
stmt, args, err := queries.toQuery(query).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInvalidArgument(err, "QUERY-fajp8", "Errors.Query.InvalidRequest")
|
||||
}
|
||||
|
||||
rows, err := q.client.QueryContext(ctx, stmt, args...)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-aJnZL", "Errors.Internal")
|
||||
}
|
||||
apps, err := scan(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apps.LatestSequence, err = q.latestSequence(ctx, appsTable)
|
||||
return apps, err
|
||||
}
|
||||
|
||||
func (q *Queries) SearchAppIDs(ctx context.Context, queries *AppSearchQueries) ([]string, error) {
|
||||
query, scan := prepareAppIDsQuery()
|
||||
stmt, args, err := queries.toQuery(query).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInvalidArgument(err, "QUERY-fajp8", "Errors.Query.InvalidRequest")
|
||||
}
|
||||
|
||||
rows, err := q.client.QueryContext(ctx, stmt, args...)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-aJnZL", "Errors.Internal")
|
||||
}
|
||||
return scan(rows)
|
||||
}
|
||||
|
||||
func NewAppNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
|
||||
return NewTextQuery(AppColumnName, value, method)
|
||||
}
|
||||
|
||||
func NewAppProjectIDSearchQuery(id string) (SearchQuery, error) {
|
||||
return NewTextQuery(AppColumnProjectID, id, TextEquals)
|
||||
}
|
||||
|
||||
func prepareAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
return sq.Select(
|
||||
AppColumnID.identifier(),
|
||||
AppColumnName.identifier(),
|
||||
AppColumnProjectID.identifier(),
|
||||
AppColumnCreationDate.identifier(),
|
||||
AppColumnChangeDate.identifier(),
|
||||
AppColumnResourceOwner.identifier(),
|
||||
AppColumnState.identifier(),
|
||||
AppColumnSequence.identifier(),
|
||||
|
||||
AppAPIConfigColumnAppID.identifier(),
|
||||
AppAPIConfigColumnClientID.identifier(),
|
||||
AppAPIConfigColumnAuthMethod.identifier(),
|
||||
|
||||
AppOIDCConfigColumnAppID.identifier(),
|
||||
AppOIDCConfigColumnVersion.identifier(),
|
||||
AppOIDCConfigColumnClientID.identifier(),
|
||||
AppOIDCConfigColumnRedirectUris.identifier(),
|
||||
AppOIDCConfigColumnResponseTypes.identifier(),
|
||||
AppOIDCConfigColumnGrantTypes.identifier(),
|
||||
AppOIDCConfigColumnApplicationType.identifier(),
|
||||
AppOIDCConfigColumnAuthMethodType.identifier(),
|
||||
AppOIDCConfigColumnPostLogoutRedirectUris.identifier(),
|
||||
AppOIDCConfigColumnDevMode.identifier(),
|
||||
AppOIDCConfigColumnAccessTokenType.identifier(),
|
||||
AppOIDCConfigColumnAccessTokenRoleAssertion.identifier(),
|
||||
AppOIDCConfigColumnIDTokenRoleAssertion.identifier(),
|
||||
AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(),
|
||||
AppOIDCConfigColumnClockSkew.identifier(),
|
||||
AppOIDCConfigColumnAdditionalOrigins.identifier(),
|
||||
).From(appsTable.identifier()).
|
||||
LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)).
|
||||
LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)).
|
||||
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) {
|
||||
app := new(App)
|
||||
|
||||
var (
|
||||
apiConfig = sqlAPIConfig{}
|
||||
oidcConfig = sqlOIDCConfig{}
|
||||
)
|
||||
|
||||
err := row.Scan(
|
||||
&app.ID,
|
||||
&app.Name,
|
||||
&app.ProjectID,
|
||||
&app.CreationDate,
|
||||
&app.ChangeDate,
|
||||
&app.ResourceOwner,
|
||||
&app.State,
|
||||
&app.Sequence,
|
||||
|
||||
&apiConfig.appID,
|
||||
&apiConfig.clientID,
|
||||
&apiConfig.authMethod,
|
||||
|
||||
&oidcConfig.appID,
|
||||
&oidcConfig.version,
|
||||
&oidcConfig.clientID,
|
||||
&oidcConfig.redirectUris,
|
||||
&oidcConfig.responseTypes,
|
||||
&oidcConfig.grantTypes,
|
||||
&oidcConfig.applicationType,
|
||||
&oidcConfig.authMethodType,
|
||||
&oidcConfig.postLogoutRedirectUris,
|
||||
&oidcConfig.devMode,
|
||||
&oidcConfig.accessTokenType,
|
||||
&oidcConfig.accessTokenRoleAssertion,
|
||||
&oidcConfig.iDTokenRoleAssertion,
|
||||
&oidcConfig.iDTokenUserinfoAssertion,
|
||||
&oidcConfig.clockSkew,
|
||||
&oidcConfig.additionalOrigins,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if errs.Is(err, sql.ErrNoRows) {
|
||||
return nil, errors.ThrowNotFound(err, "QUERY-pCP8P", "Errors.App.NotExisting")
|
||||
}
|
||||
return nil, errors.ThrowInternal(err, "QUERY-0R2Nw", "Errors.Internal")
|
||||
}
|
||||
|
||||
apiConfig.set(app)
|
||||
oidcConfig.set(app)
|
||||
|
||||
return app, nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareProjectIDByAppQuery() (sq.SelectBuilder, func(*sql.Row) (projectID string, err error)) {
|
||||
return sq.Select(
|
||||
AppColumnProjectID.identifier(),
|
||||
).From(appsTable.identifier()).
|
||||
LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)).
|
||||
LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)).
|
||||
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (projectID string, err error) {
|
||||
err = row.Scan(
|
||||
&projectID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if errs.Is(err, sql.ErrNoRows) {
|
||||
return "", errors.ThrowNotFound(err, "QUERY-aKcc2", "Errors.Project.NotExisting")
|
||||
}
|
||||
return "", errors.ThrowInternal(err, "QUERY-3A5TG", "Errors.Internal")
|
||||
}
|
||||
|
||||
return projectID, nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareProjectByAppQuery() (sq.SelectBuilder, func(*sql.Row) (*Project, error)) {
|
||||
return sq.Select(
|
||||
ProjectColumnID.identifier(),
|
||||
ProjectColumnCreationDate.identifier(),
|
||||
ProjectColumnChangeDate.identifier(),
|
||||
ProjectColumnResourceOwner.identifier(),
|
||||
ProjectColumnState.identifier(),
|
||||
ProjectColumnSequence.identifier(),
|
||||
ProjectColumnName.identifier(),
|
||||
ProjectColumnProjectRoleAssertion.identifier(),
|
||||
ProjectColumnProjectRoleCheck.identifier(),
|
||||
ProjectColumnHasProjectCheck.identifier(),
|
||||
ProjectColumnPrivateLabelingSetting.identifier(),
|
||||
).From(projectsTable.identifier()).
|
||||
Join(join(AppColumnProjectID, ProjectColumnID)).
|
||||
LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)).
|
||||
LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*Project, error) {
|
||||
p := new(Project)
|
||||
err := row.Scan(
|
||||
&p.ID,
|
||||
&p.CreationDate,
|
||||
&p.ChangeDate,
|
||||
&p.ResourceOwner,
|
||||
&p.State,
|
||||
&p.Sequence,
|
||||
&p.Name,
|
||||
&p.ProjectRoleAssertion,
|
||||
&p.ProjectRoleCheck,
|
||||
&p.HasProjectCheck,
|
||||
&p.PrivateLabelingSetting,
|
||||
)
|
||||
if err != nil {
|
||||
if errs.Is(err, sql.ErrNoRows) {
|
||||
return nil, errors.ThrowNotFound(err, "QUERY-fk2fs", "Errors.Project.NotFound")
|
||||
}
|
||||
return nil, errors.ThrowInternal(err, "QUERY-dj2FF", "Errors.Internal")
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareAppsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Apps, error)) {
|
||||
return sq.Select(
|
||||
AppColumnID.identifier(),
|
||||
AppColumnName.identifier(),
|
||||
AppColumnProjectID.identifier(),
|
||||
AppColumnCreationDate.identifier(),
|
||||
AppColumnChangeDate.identifier(),
|
||||
AppColumnResourceOwner.identifier(),
|
||||
AppColumnState.identifier(),
|
||||
AppColumnSequence.identifier(),
|
||||
|
||||
AppAPIConfigColumnAppID.identifier(),
|
||||
AppAPIConfigColumnClientID.identifier(),
|
||||
AppAPIConfigColumnAuthMethod.identifier(),
|
||||
|
||||
AppOIDCConfigColumnAppID.identifier(),
|
||||
AppOIDCConfigColumnVersion.identifier(),
|
||||
AppOIDCConfigColumnClientID.identifier(),
|
||||
AppOIDCConfigColumnRedirectUris.identifier(),
|
||||
AppOIDCConfigColumnResponseTypes.identifier(),
|
||||
AppOIDCConfigColumnGrantTypes.identifier(),
|
||||
AppOIDCConfigColumnApplicationType.identifier(),
|
||||
AppOIDCConfigColumnAuthMethodType.identifier(),
|
||||
AppOIDCConfigColumnPostLogoutRedirectUris.identifier(),
|
||||
AppOIDCConfigColumnDevMode.identifier(),
|
||||
AppOIDCConfigColumnAccessTokenType.identifier(),
|
||||
AppOIDCConfigColumnAccessTokenRoleAssertion.identifier(),
|
||||
AppOIDCConfigColumnIDTokenRoleAssertion.identifier(),
|
||||
AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(),
|
||||
AppOIDCConfigColumnClockSkew.identifier(),
|
||||
AppOIDCConfigColumnAdditionalOrigins.identifier(),
|
||||
countColumn.identifier(),
|
||||
).From(appsTable.identifier()).
|
||||
LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)).
|
||||
LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)).
|
||||
PlaceholderFormat(sq.Dollar), func(row *sql.Rows) (*Apps, error) {
|
||||
apps := &Apps{Apps: []*App{}}
|
||||
|
||||
for row.Next() {
|
||||
app := new(App)
|
||||
var (
|
||||
apiConfig = sqlAPIConfig{}
|
||||
oidcConfig = sqlOIDCConfig{}
|
||||
)
|
||||
|
||||
err := row.Scan(
|
||||
&app.ID,
|
||||
&app.Name,
|
||||
&app.ProjectID,
|
||||
&app.CreationDate,
|
||||
&app.ChangeDate,
|
||||
&app.ResourceOwner,
|
||||
&app.State,
|
||||
&app.Sequence,
|
||||
|
||||
&apiConfig.appID,
|
||||
&apiConfig.clientID,
|
||||
&apiConfig.authMethod,
|
||||
|
||||
&oidcConfig.appID,
|
||||
&oidcConfig.version,
|
||||
&oidcConfig.clientID,
|
||||
&oidcConfig.redirectUris,
|
||||
&oidcConfig.responseTypes,
|
||||
&oidcConfig.grantTypes,
|
||||
&oidcConfig.applicationType,
|
||||
&oidcConfig.authMethodType,
|
||||
&oidcConfig.postLogoutRedirectUris,
|
||||
&oidcConfig.devMode,
|
||||
&oidcConfig.accessTokenType,
|
||||
&oidcConfig.accessTokenRoleAssertion,
|
||||
&oidcConfig.iDTokenRoleAssertion,
|
||||
&oidcConfig.iDTokenUserinfoAssertion,
|
||||
&oidcConfig.clockSkew,
|
||||
&oidcConfig.additionalOrigins,
|
||||
&apps.Count,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-0R2Nw", "Errors.Internal")
|
||||
}
|
||||
|
||||
apiConfig.set(app)
|
||||
oidcConfig.set(app)
|
||||
|
||||
apps.Apps = append(apps.Apps, app)
|
||||
}
|
||||
|
||||
return apps, nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareAppIDsQuery() (sq.SelectBuilder, func(*sql.Rows) ([]string, error)) {
|
||||
return sq.Select(
|
||||
AppColumnID.identifier(),
|
||||
).From(appsTable.identifier()).
|
||||
LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)).
|
||||
LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)).
|
||||
PlaceholderFormat(sq.Dollar), func(row *sql.Rows) ([]string, error) {
|
||||
ids := []string{}
|
||||
|
||||
for row.Next() {
|
||||
var id string
|
||||
if err := row.Scan(&id); err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-0R2Nw", "Errors.Internal")
|
||||
}
|
||||
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
}
|
||||
|
||||
type sqlOIDCConfig struct {
|
||||
appID sql.NullString
|
||||
version sql.NullInt32
|
||||
clientID sql.NullString
|
||||
redirectUris pq.StringArray
|
||||
applicationType sql.NullInt16
|
||||
authMethodType sql.NullInt16
|
||||
postLogoutRedirectUris pq.StringArray
|
||||
devMode sql.NullBool
|
||||
accessTokenType sql.NullInt16
|
||||
accessTokenRoleAssertion sql.NullBool
|
||||
iDTokenRoleAssertion sql.NullBool
|
||||
iDTokenUserinfoAssertion sql.NullBool
|
||||
clockSkew sql.NullInt64
|
||||
additionalOrigins pq.StringArray
|
||||
responseTypes pq.Int32Array
|
||||
grantTypes pq.Int32Array
|
||||
}
|
||||
|
||||
func (c sqlOIDCConfig) set(app *App) {
|
||||
if !c.appID.Valid {
|
||||
return
|
||||
}
|
||||
app.OIDCConfig = &OIDCApp{
|
||||
Version: domain.OIDCVersion(c.version.Int32),
|
||||
ClientID: c.clientID.String,
|
||||
RedirectURIs: c.redirectUris,
|
||||
AppType: domain.OIDCApplicationType(c.applicationType.Int16),
|
||||
AuthMethodType: domain.OIDCAuthMethodType(c.authMethodType.Int16),
|
||||
PostLogoutRedirectURIs: c.postLogoutRedirectUris,
|
||||
IsDevMode: c.devMode.Bool,
|
||||
AccessTokenType: domain.OIDCTokenType(c.accessTokenType.Int16),
|
||||
AssertAccessTokenRole: c.accessTokenRoleAssertion.Bool,
|
||||
AssertIDTokenRole: c.iDTokenRoleAssertion.Bool,
|
||||
AssertIDTokenUserinfo: c.iDTokenUserinfoAssertion.Bool,
|
||||
ClockSkew: time.Duration(c.clockSkew.Int64),
|
||||
AdditionalOrigins: c.additionalOrigins,
|
||||
ResponseTypes: oidcResponseTypesToDomain(c.responseTypes),
|
||||
GrantTypes: oidcGrantTypesToDomain(c.grantTypes),
|
||||
}
|
||||
compliance := domain.GetOIDCCompliance(app.OIDCConfig.Version, app.OIDCConfig.AppType, app.OIDCConfig.GrantTypes, app.OIDCConfig.ResponseTypes, app.OIDCConfig.AuthMethodType, app.OIDCConfig.RedirectURIs)
|
||||
app.OIDCConfig.ComplianceProblems = compliance.Problems
|
||||
|
||||
var err error
|
||||
app.OIDCConfig.AllowedOrigins, err = domain.OIDCOriginAllowList(app.OIDCConfig.RedirectURIs, app.OIDCConfig.AdditionalOrigins)
|
||||
logging.LogWithFields("app", app.ID).OnError(err).Warn("unable to set allowed origins")
|
||||
}
|
||||
|
||||
type sqlAPIConfig struct {
|
||||
appID sql.NullString
|
||||
clientID sql.NullString
|
||||
authMethod sql.NullInt16
|
||||
}
|
||||
|
||||
func (c sqlAPIConfig) set(app *App) {
|
||||
if !c.appID.Valid {
|
||||
return
|
||||
}
|
||||
app.APIConfig = &APIApp{
|
||||
ClientID: c.clientID.String,
|
||||
AuthMethodType: domain.APIAuthMethodType(c.authMethod.Int16),
|
||||
}
|
||||
}
|
||||
|
||||
func oidcResponseTypesToDomain(t pq.Int32Array) []domain.OIDCResponseType {
|
||||
types := make([]domain.OIDCResponseType, len(t))
|
||||
for i, typ := range t {
|
||||
types[i] = domain.OIDCResponseType(typ)
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
func oidcGrantTypesToDomain(t pq.Int32Array) []domain.OIDCGrantType {
|
||||
types := make([]domain.OIDCGrantType, len(t))
|
||||
for i, typ := range t {
|
||||
types[i] = domain.OIDCGrantType(typ)
|
||||
}
|
||||
return types
|
||||
}
|
1699
internal/query/app_test.go
Normal file
1699
internal/query/app_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,22 @@ import (
|
||||
errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
projectCols = []string{
|
||||
"id",
|
||||
"creation_date",
|
||||
"change_date",
|
||||
"resource_owner",
|
||||
"state",
|
||||
"sequence",
|
||||
"name",
|
||||
"project_role_assertion",
|
||||
"project_role_check",
|
||||
"has_project_check",
|
||||
"private_labeling_setting",
|
||||
}
|
||||
)
|
||||
|
||||
func Test_ProjectPrepares(t *testing.T) {
|
||||
type want struct {
|
||||
sqlExpectations sqlExpectation
|
||||
|
@ -815,6 +815,7 @@ EventTypes:
|
||||
removed: Aktion gelöscht
|
||||
Application:
|
||||
OIDC:
|
||||
UnsupportedVersion: Deine OIDC Version wird nicht unterstützt
|
||||
V1:
|
||||
NotCompliant: Deine Konfiguration ist nicht konform und weicht vom OIDC 1.0 Standard ab.
|
||||
NoRedirectUris: Es muss mindestens eine Redirect URI erfasst sein.
|
||||
|
@ -812,6 +812,7 @@ EventTypes:
|
||||
removed: Action removed
|
||||
Application:
|
||||
OIDC:
|
||||
UnsupportedVersion: Your OIDC version is not supported
|
||||
V1:
|
||||
NotCompliant: Your configuration is not compliant and differs from OIDC 1.0 standard.
|
||||
NoRedirectUris: At least one redirect uri must be registered.
|
||||
|
@ -810,6 +810,7 @@ EventTypes:
|
||||
removed: Azione rimossa
|
||||
Application:
|
||||
OIDC:
|
||||
UnsupportedVersion: La tua versione di OIDC non è supportata
|
||||
V1:
|
||||
NotCompliant: La tua configurazione non è conforme e differisce dallo standard OIDC 1.0.
|
||||
NoRedirectUris: Deve essere registrato almeno un URI di reindirizzamento.
|
||||
|
Loading…
Reference in New Issue
Block a user