mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 02:54:20 +00:00
feat(queries): user IDP links (#2751)
This commit is contained in:
parent
2ad03285f1
commit
303d4945a7
@ -9,14 +9,18 @@ import (
|
||||
)
|
||||
|
||||
func (s *Server) ListMyLinkedIDPs(ctx context.Context, req *auth_pb.ListMyLinkedIDPsRequest) (*auth_pb.ListMyLinkedIDPsResponse, error) {
|
||||
idps, err := s.repo.SearchMyExternalIDPs(ctx, ListMyLinkedIDPsRequestToModel(req))
|
||||
q, err := ListMyLinkedIDPsRequestToQuery(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idps, err := s.query.UserIDPLinks(ctx, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &auth_pb.ListMyLinkedIDPsResponse{
|
||||
Result: idp_grpc.IDPsToUserLinkPb(idps.Result),
|
||||
Result: idp_grpc.IDPUserLinksToPb(idps.Links),
|
||||
Details: object.ToListDetails(
|
||||
idps.TotalResult,
|
||||
idps.Count,
|
||||
idps.Sequence,
|
||||
idps.Timestamp,
|
||||
),
|
||||
|
@ -3,19 +3,27 @@ package auth
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/user/model"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
|
||||
)
|
||||
|
||||
func ListMyLinkedIDPsRequestToModel(req *auth_pb.ListMyLinkedIDPsRequest) *model.ExternalIDPSearchRequest {
|
||||
func ListMyLinkedIDPsRequestToQuery(ctx context.Context, req *auth_pb.ListMyLinkedIDPsRequest) (*query.UserIDPLinksSearchQuery, error) {
|
||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||
return &model.ExternalIDPSearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
q, err := query.NewUserIDPLinksUserIDSearchQuery(authz.GetCtxData(ctx).UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &query.UserIDPLinksSearchQuery{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
},
|
||||
Queries: []query.SearchQuery{q},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func RemoveMyLinkedIDPRequestToDomain(ctx context.Context, req *auth_pb.RemoveMyLinkedIDPRequest) *domain.UserIDPLink {
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
user_model "github.com/caos/zitadel/internal/user/model"
|
||||
idp_pb "github.com/caos/zitadel/pkg/grpc/idp"
|
||||
)
|
||||
|
||||
@ -61,31 +60,30 @@ func ExternalIDPViewToLoginPolicyLinkPb(link *iam_model.IDPProviderView) *idp_pb
|
||||
return &idp_pb.IDPLoginPolicyLink{
|
||||
IdpId: link.IDPConfigID,
|
||||
IdpName: link.Name,
|
||||
IdpType: IDPTypeToPb(link.IDPConfigType),
|
||||
IdpType: IDPTypeViewToPb(link.IDPConfigType),
|
||||
}
|
||||
}
|
||||
|
||||
func IDPsToUserLinkPb(res []*user_model.ExternalIDPView) []*idp_pb.IDPUserLink {
|
||||
func IDPUserLinksToPb(res []*query.UserIDPLink) []*idp_pb.IDPUserLink {
|
||||
links := make([]*idp_pb.IDPUserLink, len(res))
|
||||
for i, link := range res {
|
||||
links[i] = ExternalIDPViewToUserLinkPb(link)
|
||||
links[i] = IDPUserLinkToPb(link)
|
||||
}
|
||||
return links
|
||||
}
|
||||
|
||||
func ExternalIDPViewToUserLinkPb(link *user_model.ExternalIDPView) *idp_pb.IDPUserLink {
|
||||
func IDPUserLinkToPb(link *query.UserIDPLink) *idp_pb.IDPUserLink {
|
||||
return &idp_pb.IDPUserLink{
|
||||
UserId: link.UserID,
|
||||
IdpId: link.IDPConfigID,
|
||||
IdpId: link.IDPID,
|
||||
IdpName: link.IDPName,
|
||||
ProvidedUserId: link.ExternalUserID,
|
||||
ProvidedUserName: link.UserDisplayName,
|
||||
//TODO: as soon as saml is implemented we need to switch here
|
||||
//IdpType: IDPTypeToPb(link.Type),
|
||||
ProvidedUserId: link.ProvidedUserID,
|
||||
ProvidedUserName: link.ProvidedUsername,
|
||||
IdpType: IDPTypeToPb(link.IDPType),
|
||||
}
|
||||
}
|
||||
|
||||
func IDPTypeToPb(idpType iam_model.IdpConfigType) idp_pb.IDPType {
|
||||
func IDPTypeViewToPb(idpType iam_model.IdpConfigType) idp_pb.IDPType {
|
||||
switch idpType {
|
||||
case iam_model.IDPConfigTypeOIDC:
|
||||
return idp_pb.IDPType_IDP_TYPE_OIDC
|
||||
@ -98,6 +96,19 @@ func IDPTypeToPb(idpType iam_model.IdpConfigType) idp_pb.IDPType {
|
||||
}
|
||||
}
|
||||
|
||||
func IDPTypeToPb(idpType domain.IDPConfigType) idp_pb.IDPType {
|
||||
switch idpType {
|
||||
case domain.IDPConfigTypeOIDC:
|
||||
return idp_pb.IDPType_IDP_TYPE_OIDC
|
||||
case domain.IDPConfigTypeSAML:
|
||||
return idp_pb.IDPType_IDP_TYPE_UNSPECIFIED
|
||||
case domain.IDPConfigTypeJWT:
|
||||
return idp_pb.IDPType_IDP_TYPE_JWT
|
||||
default:
|
||||
return idp_pb.IDPType_IDP_TYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func IDPStateToPb(state domain.IDPConfigState) idp_pb.IDPState {
|
||||
switch state {
|
||||
case domain.IDPConfigStateActive:
|
||||
|
@ -606,14 +606,18 @@ func (s *Server) RemoveMachineKey(ctx context.Context, req *mgmt_pb.RemoveMachin
|
||||
}
|
||||
|
||||
func (s *Server) ListHumanLinkedIDPs(ctx context.Context, req *mgmt_pb.ListHumanLinkedIDPsRequest) (*mgmt_pb.ListHumanLinkedIDPsResponse, error) {
|
||||
res, err := s.user.SearchExternalIDPs(ctx, ListHumanLinkedIDPsRequestToModel(req))
|
||||
queries, err := ListHumanLinkedIDPsRequestToQuery(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := s.query.UserIDPLinks(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.ListHumanLinkedIDPsResponse{
|
||||
Result: idp_grpc.IDPsToUserLinkPb(res.Result),
|
||||
Result: idp_grpc.IDPUserLinksToPb(res.Links),
|
||||
Details: obj_grpc.ToListDetails(
|
||||
res.TotalResult,
|
||||
res.Count,
|
||||
res.Sequence,
|
||||
res.Timestamp,
|
||||
),
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
key_model "github.com/caos/zitadel/internal/key/model"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
user_model "github.com/caos/zitadel/internal/user/model"
|
||||
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
|
||||
user_pb "github.com/caos/zitadel/pkg/grpc/user"
|
||||
@ -234,14 +235,24 @@ func RemoveHumanLinkedIDPRequestToDomain(ctx context.Context, req *mgmt_pb.Remov
|
||||
}
|
||||
}
|
||||
|
||||
func ListHumanLinkedIDPsRequestToModel(req *mgmt_pb.ListHumanLinkedIDPsRequest) *user_model.ExternalIDPSearchRequest {
|
||||
func ListHumanLinkedIDPsRequestToQuery(ctx context.Context, req *mgmt_pb.ListHumanLinkedIDPsRequest) (*query.UserIDPLinksSearchQuery, error) {
|
||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||
return &user_model.ExternalIDPSearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
Queries: []*user_model.ExternalIDPSearchQuery{{Key: user_model.ExternalIDPSearchKeyUserID, Method: domain.SearchMethodEquals, Value: req.UserId}},
|
||||
userQuery, err := query.NewUserIDPLinksUserIDSearchQuery(req.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resourceOwnerQuery, err := query.NewUserIDPLinksResourceOwnerSearchQuery(authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &query.UserIDPLinksSearchQuery{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
},
|
||||
Queries: []query.SearchQuery{userQuery, resourceOwnerQuery},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ListUserMembershipsRequestToModel(req *mgmt_pb.ListUserMembershipsRequest) (*user_model.UserMembershipSearchRequest, error) {
|
||||
|
@ -78,6 +78,7 @@ const (
|
||||
|
||||
//count is for validation
|
||||
idpConfigTypeCount
|
||||
IDPConfigTypeUnspecified IDPConfigType = -1
|
||||
)
|
||||
|
||||
func (f IDPConfigType) Valid() bool {
|
||||
|
@ -98,6 +98,10 @@ var (
|
||||
name: projection.IDPAutoRegisterCol,
|
||||
table: idpTable,
|
||||
}
|
||||
IDPTypeCol = Column{
|
||||
name: projection.IDPTypeCol,
|
||||
table: idpTable,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -134,6 +134,7 @@ const (
|
||||
IDPStylingTypeCol = "styling_type"
|
||||
IDPOwnerTypeCol = "owner_type"
|
||||
IDPAutoRegisterCol = "auto_register"
|
||||
IDPTypeCol = "type"
|
||||
|
||||
OIDCConfigIDPIDCol = "idp_id"
|
||||
OIDCConfigClientIDCol = "client_id"
|
||||
@ -311,6 +312,7 @@ func (p *IDPProjection) reduceOIDCConfigAdded(event eventstore.EventReader) (*ha
|
||||
[]handler.Column{
|
||||
handler.NewCol(IDPChangeDateCol, idpEvent.CreationDate()),
|
||||
handler.NewCol(IDPSequenceCol, idpEvent.Sequence()),
|
||||
handler.NewCol(IDPTypeCol, domain.IDPConfigTypeOIDC),
|
||||
},
|
||||
[]handler.Condition{
|
||||
handler.NewCond(IDPIDCol, idpEvent.IDPConfigID),
|
||||
@ -413,6 +415,7 @@ func (p *IDPProjection) reduceJWTConfigAdded(event eventstore.EventReader) (*han
|
||||
[]handler.Column{
|
||||
handler.NewCol(IDPChangeDateCol, idpEvent.CreationDate()),
|
||||
handler.NewCol(IDPSequenceCol, idpEvent.Sequence()),
|
||||
handler.NewCol(IDPTypeCol, domain.IDPConfigTypeJWT),
|
||||
},
|
||||
[]handler.Condition{
|
||||
handler.NewCond(IDPIDCol, idpEvent.IDPConfigID),
|
||||
|
@ -227,10 +227,11 @@ func TestIDPProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE zitadel.projections.idps SET (change_date, sequence) = ($1, $2) WHERE (id = $3)",
|
||||
expectedStmt: "UPDATE zitadel.projections.idps SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
domain.IDPConfigTypeOIDC,
|
||||
"idp-config-id",
|
||||
},
|
||||
},
|
||||
@ -353,10 +354,11 @@ func TestIDPProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE zitadel.projections.idps SET (change_date, sequence) = ($1, $2) WHERE (id = $3)",
|
||||
expectedStmt: "UPDATE zitadel.projections.idps SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
domain.IDPConfigTypeJWT,
|
||||
"idp-config-id",
|
||||
},
|
||||
},
|
||||
@ -643,10 +645,11 @@ func TestIDPProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE zitadel.projections.idps SET (change_date, sequence) = ($1, $2) WHERE (id = $3)",
|
||||
expectedStmt: "UPDATE zitadel.projections.idps SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
domain.IDPConfigTypeOIDC,
|
||||
"idp-config-id",
|
||||
},
|
||||
},
|
||||
@ -769,10 +772,11 @@ func TestIDPProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE zitadel.projections.idps SET (change_date, sequence) = ($1, $2) WHERE (id = $3)",
|
||||
expectedStmt: "UPDATE zitadel.projections.idps SET (change_date, sequence, type) = ($1, $2, $3) WHERE (id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
domain.IDPConfigTypeJWT,
|
||||
"idp-config-id",
|
||||
},
|
||||
},
|
||||
|
@ -15,10 +15,6 @@ type IDPUserLinkProjection struct {
|
||||
crdb.StatementHandler
|
||||
}
|
||||
|
||||
const (
|
||||
IDPUserLinkTable = "zitadel.projections.idp_user_links"
|
||||
)
|
||||
|
||||
func NewIDPUserLinkProjection(ctx context.Context, config crdb.StatementHandlerConfig) *IDPUserLinkProjection {
|
||||
p := &IDPUserLinkProjection{}
|
||||
config.ProjectionName = IDPUserLinkTable
|
||||
@ -50,6 +46,7 @@ func (p *IDPUserLinkProjection) reducers() []handler.AggregateReducer {
|
||||
}
|
||||
|
||||
const (
|
||||
IDPUserLinkTable = "zitadel.projections.idp_user_links"
|
||||
IDPUserLinkIDPIDCol = "idp_id"
|
||||
IDPUserLinkUserIDCol = "user_id"
|
||||
IDPUserLinkExternalUserIDCol = "external_user_id"
|
||||
|
158
internal/query/user_idp_link.go
Normal file
158
internal/query/user_idp_link.go
Normal file
@ -0,0 +1,158 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/query/projection"
|
||||
)
|
||||
|
||||
type UserIDPLink struct {
|
||||
IDPID string
|
||||
UserID string
|
||||
IDPName string
|
||||
ProvidedUserID string
|
||||
ProvidedUsername string
|
||||
IDPType domain.IDPConfigType
|
||||
}
|
||||
|
||||
type UserIDPLinks struct {
|
||||
SearchResponse
|
||||
Links []*UserIDPLink
|
||||
}
|
||||
|
||||
type UserIDPLinksSearchQuery struct {
|
||||
SearchRequest
|
||||
Queries []SearchQuery
|
||||
}
|
||||
|
||||
func (q *UserIDPLinksSearchQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||
query = q.SearchRequest.toQuery(query)
|
||||
for _, q := range q.Queries {
|
||||
query = q.toQuery(query)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
var (
|
||||
idpUserLinkTable = table{
|
||||
name: projection.IDPUserLinkTable,
|
||||
}
|
||||
IDPUserLinkIDPIDCol = Column{
|
||||
name: projection.IDPUserLinkIDPIDCol,
|
||||
table: idpUserLinkTable,
|
||||
}
|
||||
IDPUserLinkUserIDCol = Column{
|
||||
name: projection.IDPUserLinkUserIDCol,
|
||||
table: idpUserLinkTable,
|
||||
}
|
||||
IDPUserLinkExternalUserIDCol = Column{
|
||||
name: projection.IDPUserLinkExternalUserIDCol,
|
||||
table: idpUserLinkTable,
|
||||
}
|
||||
IDPUserLinkCreationDateCol = Column{
|
||||
name: projection.IDPUserLinkCreationDateCol,
|
||||
table: idpUserLinkTable,
|
||||
}
|
||||
IDPUserLinkChangeDateCol = Column{
|
||||
name: projection.IDPUserLinkChangeDateCol,
|
||||
table: idpUserLinkTable,
|
||||
}
|
||||
IDPUserLinkSequenceCol = Column{
|
||||
name: projection.IDPUserLinkSequenceCol,
|
||||
table: idpUserLinkTable,
|
||||
}
|
||||
IDPUserLinkResourceOwnerCol = Column{
|
||||
name: projection.IDPUserLinkResourceOwnerCol,
|
||||
table: idpUserLinkTable,
|
||||
}
|
||||
IDPUserLinkDisplayNameCol = Column{
|
||||
name: projection.IDPUserLinkDisplayNameCol,
|
||||
table: idpUserLinkTable,
|
||||
}
|
||||
)
|
||||
|
||||
func (q *Queries) UserIDPLinks(ctx context.Context, queries *UserIDPLinksSearchQuery) (idps *UserIDPLinks, err error) {
|
||||
query, scan := prepareUserIDPLinksQuery()
|
||||
stmt, args, err := queries.toQuery(query).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInvalidArgument(err, "QUERY-4zzFK", "Errors.Query.InvalidRequest")
|
||||
}
|
||||
|
||||
rows, err := q.client.QueryContext(ctx, stmt, args...)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-C1E4D", "Errors.Internal")
|
||||
}
|
||||
idps, err = scan(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idps.LatestSequence, err = q.latestSequence(ctx, idpUserLinkTable)
|
||||
return idps, err
|
||||
}
|
||||
|
||||
func NewUserIDPLinksUserIDSearchQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(IDPUserLinkUserIDCol, value, TextEquals)
|
||||
}
|
||||
|
||||
func NewUserIDPLinksResourceOwnerSearchQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(IDPUserLinkResourceOwnerCol, value, TextEquals)
|
||||
}
|
||||
|
||||
func prepareUserIDPLinksQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserIDPLinks, error)) {
|
||||
return sq.Select(
|
||||
IDPUserLinkIDPIDCol.identifier(),
|
||||
IDPUserLinkUserIDCol.identifier(),
|
||||
IDPNameCol.identifier(),
|
||||
IDPUserLinkExternalUserIDCol.identifier(),
|
||||
IDPUserLinkDisplayNameCol.identifier(),
|
||||
IDPTypeCol.identifier(),
|
||||
countColumn.identifier()).
|
||||
From(idpUserLinkTable.identifier()).
|
||||
LeftJoin(join(IDPIDCol, IDPUserLinkIDPIDCol)).PlaceholderFormat(sq.Dollar),
|
||||
func(rows *sql.Rows) (*UserIDPLinks, error) {
|
||||
idps := make([]*UserIDPLink, 0)
|
||||
var count uint64
|
||||
for rows.Next() {
|
||||
var (
|
||||
idpName = sql.NullString{}
|
||||
idpType = sql.NullInt16{}
|
||||
idp = new(UserIDPLink)
|
||||
)
|
||||
err := rows.Scan(
|
||||
&idp.IDPID,
|
||||
&idp.UserID,
|
||||
&idpName,
|
||||
&idp.ProvidedUserID,
|
||||
&idp.ProvidedUsername,
|
||||
&idpType,
|
||||
&count,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idp.IDPName = idpName.String
|
||||
//IDPType 0 is oidc so we have to set unspecified manually
|
||||
if idpType.Valid {
|
||||
idp.IDPType = domain.IDPConfigType(idpType.Int16)
|
||||
} else {
|
||||
idp.IDPType = domain.IDPConfigTypeUnspecified
|
||||
}
|
||||
idps = append(idps, idp)
|
||||
}
|
||||
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-nwx6U", "Errors.Query.CloseRows")
|
||||
}
|
||||
|
||||
return &UserIDPLinks{
|
||||
Links: idps,
|
||||
SearchResponse: SearchResponse{
|
||||
Count: count,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
139
internal/query/user_idp_link_test.go
Normal file
139
internal/query/user_idp_link_test.go
Normal file
@ -0,0 +1,139 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
var (
|
||||
userIDPLinksQuery = regexp.QuoteMeta(`SELECT zitadel.projections.idp_user_links.idp_id,` +
|
||||
` zitadel.projections.idp_user_links.user_id,` +
|
||||
` zitadel.projections.idps.name,` +
|
||||
` zitadel.projections.idp_user_links.external_user_id,` +
|
||||
` zitadel.projections.idp_user_links.display_name,` +
|
||||
` zitadel.projections.idps.type,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM zitadel.projections.idp_user_links` +
|
||||
` LEFT JOIN zitadel.projections.idps ON zitadel.projections.idp_user_links.idp_id = zitadel.projections.idps.id`)
|
||||
userIDPLinksCols = []string{
|
||||
"idp_id",
|
||||
"user_id",
|
||||
"name",
|
||||
"external_user_id",
|
||||
"display_name",
|
||||
"type",
|
||||
"count",
|
||||
}
|
||||
)
|
||||
|
||||
func Test_UserIDPLinkPrepares(t *testing.T) {
|
||||
type want struct {
|
||||
sqlExpectations sqlExpectation
|
||||
err checkErr
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare interface{}
|
||||
want want
|
||||
object interface{}
|
||||
}{
|
||||
{
|
||||
name: "prepareIDPsQuery found",
|
||||
prepare: prepareUserIDPLinksQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
userIDPLinksQuery,
|
||||
userIDPLinksCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"idp-id",
|
||||
"user-id",
|
||||
"idp-name",
|
||||
"external-user-id",
|
||||
"display-name",
|
||||
domain.IDPConfigTypeJWT,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &UserIDPLinks{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 1,
|
||||
},
|
||||
Links: []*UserIDPLink{
|
||||
{
|
||||
IDPID: "idp-id",
|
||||
UserID: "user-id",
|
||||
IDPName: "idp-name",
|
||||
ProvidedUserID: "external-user-id",
|
||||
ProvidedUsername: "display-name",
|
||||
IDPType: domain.IDPConfigTypeJWT,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareIDPsQuery no idp",
|
||||
prepare: prepareUserIDPLinksQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
userIDPLinksQuery,
|
||||
userIDPLinksCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"idp-id",
|
||||
"user-id",
|
||||
nil,
|
||||
"external-user-id",
|
||||
"display-name",
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &UserIDPLinks{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 1,
|
||||
},
|
||||
Links: []*UserIDPLink{
|
||||
{
|
||||
IDPID: "idp-id",
|
||||
UserID: "user-id",
|
||||
IDPName: "",
|
||||
ProvidedUserID: "external-user-id",
|
||||
ProvidedUsername: "display-name",
|
||||
IDPType: domain.IDPConfigTypeUnspecified,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareIDPsQuery sql err",
|
||||
prepare: prepareUserIDPLinksQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
userIDPLinksQuery,
|
||||
sql.ErrConnDone,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !errors.Is(err, sql.ErrConnDone) {
|
||||
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err)
|
||||
})
|
||||
}
|
||||
}
|
13
migrations/cockroach/V1.100__idp_type.sql
Normal file
13
migrations/cockroach/V1.100__idp_type.sql
Normal file
@ -0,0 +1,13 @@
|
||||
ALTER TABLE zitadel.projections.idps ADD COLUMN type INT2;
|
||||
|
||||
-- jwt-type is 2
|
||||
-- oidc-type is 0
|
||||
WITH doa AS (
|
||||
SELECT i.id, IF(o.idp_id IS NULL, 0, 2) as type
|
||||
FROM projections.idps i
|
||||
LEFT JOIN projections.idps_oidc_config o
|
||||
ON o.idp_id = i.id
|
||||
LEFT JOIN projections.idps_jwt_config j
|
||||
ON j.idp_id = i.id
|
||||
)
|
||||
UPDATE zitadel.projections.idps SET type = doa.type FROM doa WHERE doa.id = zitadel.projections.idps.id;
|
Loading…
Reference in New Issue
Block a user