mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:17:32 +00:00
feat: api v2beta to api v2 (#8283)
# Which Problems Are Solved The v2beta services are stable but not GA. # How the Problems Are Solved The v2beta services are copied to v2. The corresponding v1 and v2beta services are deprecated. # Additional Context Closes #7236 --------- Co-authored-by: Elio Bischof <elio@zitadel.com>
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
||||
)
|
||||
|
||||
var _ session.SessionServiceServer = (*Server)(nil)
|
||||
|
@@ -18,7 +18,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
objpb "github.com/zitadel/zitadel/pkg/grpc/object"
|
||||
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@@ -23,9 +23,9 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -860,7 +860,7 @@ func TestServer_SetSession_expired(t *testing.T) {
|
||||
|
||||
// ensure session expires and does not work anymore
|
||||
time.Sleep(20 * time.Second)
|
||||
_, err = Tester.Client.SessionV2.SetSession(CTX, &session.SetSessionRequest{
|
||||
_, err = Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Lifetime: durationpb.New(20 * time.Second),
|
||||
})
|
||||
@@ -944,7 +944,7 @@ func Test_ZITADEL_API_missing_authentication(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := metadata.AppendToOutgoingContext(context.Background(), "Authorization", fmt.Sprintf("Bearer %s", createResp.GetSessionToken()))
|
||||
sessionResp, err := Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: createResp.GetSessionId()})
|
||||
sessionResp, err := Client.GetSession(ctx, &session.GetSessionRequest{SessionId: createResp.GetSessionId()})
|
||||
require.Error(t, err)
|
||||
require.Nil(t, sessionResp)
|
||||
}
|
||||
@@ -953,7 +953,7 @@ func Test_ZITADEL_API_success(t *testing.T) {
|
||||
id, token, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, User.GetUserId())
|
||||
|
||||
ctx := Tester.WithAuthorizationToken(context.Background(), token)
|
||||
sessionResp, err := Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
sessionResp, err := Client.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
require.NoError(t, err)
|
||||
|
||||
webAuthN := sessionResp.GetSession().GetFactors().GetWebAuthN()
|
||||
@@ -966,17 +966,17 @@ func Test_ZITADEL_API_session_not_found(t *testing.T) {
|
||||
|
||||
// test session token works
|
||||
ctx := Tester.WithAuthorizationToken(context.Background(), token)
|
||||
_, err := Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
_, err := Client.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
require.NoError(t, err)
|
||||
|
||||
//terminate the session and test it does not work anymore
|
||||
_, err = Tester.Client.SessionV2.DeleteSession(CTX, &session.DeleteSessionRequest{
|
||||
_, err = Client.DeleteSession(CTX, &session.DeleteSessionRequest{
|
||||
SessionId: id,
|
||||
SessionToken: gu.Ptr(token),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
ctx = Tester.WithAuthorizationToken(context.Background(), token)
|
||||
_, err = Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
_, err = Client.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -985,12 +985,12 @@ func Test_ZITADEL_API_session_expired(t *testing.T) {
|
||||
|
||||
// test session token works
|
||||
ctx := Tester.WithAuthorizationToken(context.Background(), token)
|
||||
_, err := Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
_, err := Client.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
require.NoError(t, err)
|
||||
|
||||
// ensure session expires and does not work anymore
|
||||
time.Sleep(20 * time.Second)
|
||||
sessionResp, err := Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
sessionResp, err := Client.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
require.Error(t, err)
|
||||
require.Nil(t, sessionResp)
|
||||
}
|
||||
|
@@ -18,8 +18,8 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
objpb "github.com/zitadel/zitadel/pkg/grpc/object"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
|
51
internal/api/grpc/session/v2beta/server.go
Normal file
51
internal/api/grpc/session/v2beta/server.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||
)
|
||||
|
||||
var _ session.SessionServiceServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
session.UnimplementedSessionServiceServer
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
query: query,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||
session.RegisterSessionServiceServer(grpcServer, s)
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
return session.SessionService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) MethodPrefix() string {
|
||||
return session.SessionService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||
return session.SessionService_AuthMethods
|
||||
}
|
||||
|
||||
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||
return session.RegisterSessionServiceHandler
|
||||
}
|
500
internal/api/grpc/session/v2beta/session.go
Normal file
500
internal/api/grpc/session/v2beta/session.go
Normal file
@@ -0,0 +1,500 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
object "github.com/zitadel/zitadel/internal/api/grpc/object/v2beta"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
objpb "github.com/zitadel/zitadel/pkg/grpc/object"
|
||||
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||
)
|
||||
|
||||
var (
|
||||
timestampComparisons = map[objpb.TimestampQueryMethod]query.TimestampComparison{
|
||||
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_EQUALS: query.TimestampEquals,
|
||||
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_GREATER: query.TimestampGreater,
|
||||
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_GREATER_OR_EQUALS: query.TimestampGreaterOrEquals,
|
||||
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_LESS: query.TimestampLess,
|
||||
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_LESS_OR_EQUALS: query.TimestampLessOrEquals,
|
||||
}
|
||||
)
|
||||
|
||||
func (s *Server) GetSession(ctx context.Context, req *session.GetSessionRequest) (*session.GetSessionResponse, error) {
|
||||
res, err := s.query.SessionByID(ctx, true, req.GetSessionId(), req.GetSessionToken())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &session.GetSessionResponse{
|
||||
Session: sessionToPb(res),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListSessions(ctx context.Context, req *session.ListSessionsRequest) (*session.ListSessionsResponse, error) {
|
||||
queries, err := listSessionsRequestToQuery(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sessions, err := s.query.SearchSessions(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &session.ListSessionsResponse{
|
||||
Details: object.ToListDetails(sessions.SearchResponse),
|
||||
Sessions: sessionsToPb(sessions.Sessions),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) CreateSession(ctx context.Context, req *session.CreateSessionRequest) (*session.CreateSessionResponse, error) {
|
||||
checks, metadata, userAgent, lifetime, err := s.createSessionRequestToCommand(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
challengeResponse, cmds, err := s.challengesToCommand(req.GetChallenges(), checks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
set, err := s.command.CreateSession(ctx, cmds, metadata, userAgent, lifetime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &session.CreateSessionResponse{
|
||||
Details: object.DomainToDetailsPb(set.ObjectDetails),
|
||||
SessionId: set.ID,
|
||||
SessionToken: set.NewToken,
|
||||
Challenges: challengeResponse,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) SetSession(ctx context.Context, req *session.SetSessionRequest) (*session.SetSessionResponse, error) {
|
||||
checks, err := s.setSessionRequestToCommand(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
challengeResponse, cmds, err := s.challengesToCommand(req.GetChallenges(), checks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
set, err := s.command.UpdateSession(ctx, req.GetSessionId(), cmds, req.GetMetadata(), req.GetLifetime().AsDuration())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &session.SetSessionResponse{
|
||||
Details: object.DomainToDetailsPb(set.ObjectDetails),
|
||||
SessionToken: set.NewToken,
|
||||
Challenges: challengeResponse,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteSession(ctx context.Context, req *session.DeleteSessionRequest) (*session.DeleteSessionResponse, error) {
|
||||
details, err := s.command.TerminateSession(ctx, req.GetSessionId(), req.GetSessionToken())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &session.DeleteSessionResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sessionsToPb(sessions []*query.Session) []*session.Session {
|
||||
s := make([]*session.Session, len(sessions))
|
||||
for i, session := range sessions {
|
||||
s[i] = sessionToPb(session)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func sessionToPb(s *query.Session) *session.Session {
|
||||
return &session.Session{
|
||||
Id: s.ID,
|
||||
CreationDate: timestamppb.New(s.CreationDate),
|
||||
ChangeDate: timestamppb.New(s.ChangeDate),
|
||||
Sequence: s.Sequence,
|
||||
Factors: factorsToPb(s),
|
||||
Metadata: s.Metadata,
|
||||
UserAgent: userAgentToPb(s.UserAgent),
|
||||
ExpirationDate: expirationToPb(s.Expiration),
|
||||
}
|
||||
}
|
||||
|
||||
func userAgentToPb(ua domain.UserAgent) *session.UserAgent {
|
||||
if ua.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := &session.UserAgent{
|
||||
FingerprintId: ua.FingerprintID,
|
||||
Description: ua.Description,
|
||||
}
|
||||
if ua.IP != nil {
|
||||
out.Ip = gu.Ptr(ua.IP.String())
|
||||
}
|
||||
if ua.Header == nil {
|
||||
return out
|
||||
}
|
||||
out.Header = make(map[string]*session.UserAgent_HeaderValues, len(ua.Header))
|
||||
for k, v := range ua.Header {
|
||||
out.Header[k] = &session.UserAgent_HeaderValues{
|
||||
Values: v,
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func expirationToPb(expiration time.Time) *timestamppb.Timestamp {
|
||||
if expiration.IsZero() {
|
||||
return nil
|
||||
}
|
||||
return timestamppb.New(expiration)
|
||||
}
|
||||
|
||||
func factorsToPb(s *query.Session) *session.Factors {
|
||||
user := userFactorToPb(s.UserFactor)
|
||||
if user == nil {
|
||||
return nil
|
||||
}
|
||||
return &session.Factors{
|
||||
User: user,
|
||||
Password: passwordFactorToPb(s.PasswordFactor),
|
||||
WebAuthN: webAuthNFactorToPb(s.WebAuthNFactor),
|
||||
Intent: intentFactorToPb(s.IntentFactor),
|
||||
Totp: totpFactorToPb(s.TOTPFactor),
|
||||
OtpSms: otpFactorToPb(s.OTPSMSFactor),
|
||||
OtpEmail: otpFactorToPb(s.OTPEmailFactor),
|
||||
}
|
||||
}
|
||||
|
||||
func passwordFactorToPb(factor query.SessionPasswordFactor) *session.PasswordFactor {
|
||||
if factor.PasswordCheckedAt.IsZero() {
|
||||
return nil
|
||||
}
|
||||
return &session.PasswordFactor{
|
||||
VerifiedAt: timestamppb.New(factor.PasswordCheckedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func intentFactorToPb(factor query.SessionIntentFactor) *session.IntentFactor {
|
||||
if factor.IntentCheckedAt.IsZero() {
|
||||
return nil
|
||||
}
|
||||
return &session.IntentFactor{
|
||||
VerifiedAt: timestamppb.New(factor.IntentCheckedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func webAuthNFactorToPb(factor query.SessionWebAuthNFactor) *session.WebAuthNFactor {
|
||||
if factor.WebAuthNCheckedAt.IsZero() {
|
||||
return nil
|
||||
}
|
||||
return &session.WebAuthNFactor{
|
||||
VerifiedAt: timestamppb.New(factor.WebAuthNCheckedAt),
|
||||
UserVerified: factor.UserVerified,
|
||||
}
|
||||
}
|
||||
|
||||
func totpFactorToPb(factor query.SessionTOTPFactor) *session.TOTPFactor {
|
||||
if factor.TOTPCheckedAt.IsZero() {
|
||||
return nil
|
||||
}
|
||||
return &session.TOTPFactor{
|
||||
VerifiedAt: timestamppb.New(factor.TOTPCheckedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func otpFactorToPb(factor query.SessionOTPFactor) *session.OTPFactor {
|
||||
if factor.OTPCheckedAt.IsZero() {
|
||||
return nil
|
||||
}
|
||||
return &session.OTPFactor{
|
||||
VerifiedAt: timestamppb.New(factor.OTPCheckedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func userFactorToPb(factor query.SessionUserFactor) *session.UserFactor {
|
||||
if factor.UserID == "" || factor.UserCheckedAt.IsZero() {
|
||||
return nil
|
||||
}
|
||||
return &session.UserFactor{
|
||||
VerifiedAt: timestamppb.New(factor.UserCheckedAt),
|
||||
Id: factor.UserID,
|
||||
LoginName: factor.LoginName,
|
||||
DisplayName: factor.DisplayName,
|
||||
OrganizationId: factor.ResourceOwner,
|
||||
}
|
||||
}
|
||||
|
||||
func listSessionsRequestToQuery(ctx context.Context, req *session.ListSessionsRequest) (*query.SessionsSearchQueries, error) {
|
||||
offset, limit, asc := object.ListQueryToQuery(req.Query)
|
||||
queries, err := sessionQueriesToQuery(ctx, req.GetQueries())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &query.SessionsSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: fieldNameToSessionColumn(req.GetSortingColumn()),
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sessionQueriesToQuery(ctx context.Context, queries []*session.SearchQuery) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries)+1)
|
||||
for i, v := range queries {
|
||||
q[i], err = sessionQueryToQuery(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
creatorQuery, err := query.NewSessionCreatorSearchQuery(authz.GetCtxData(ctx).UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q[len(queries)] = creatorQuery
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func sessionQueryToQuery(sq *session.SearchQuery) (query.SearchQuery, error) {
|
||||
switch q := sq.Query.(type) {
|
||||
case *session.SearchQuery_IdsQuery:
|
||||
return idsQueryToQuery(q.IdsQuery)
|
||||
case *session.SearchQuery_UserIdQuery:
|
||||
return query.NewUserIDSearchQuery(q.UserIdQuery.GetId())
|
||||
case *session.SearchQuery_CreationDateQuery:
|
||||
return creationDateQueryToQuery(q.CreationDateQuery)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-Sfefs", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func idsQueryToQuery(q *session.IDsQuery) (query.SearchQuery, error) {
|
||||
return query.NewSessionIDsSearchQuery(q.Ids)
|
||||
}
|
||||
|
||||
func creationDateQueryToQuery(q *session.CreationDateQuery) (query.SearchQuery, error) {
|
||||
comparison := timestampComparisons[q.GetMethod()]
|
||||
return query.NewCreationDateQuery(q.GetCreationDate().AsTime(), comparison)
|
||||
}
|
||||
|
||||
func fieldNameToSessionColumn(field session.SessionFieldName) query.Column {
|
||||
switch field {
|
||||
case session.SessionFieldName_SESSION_FIELD_NAME_CREATION_DATE:
|
||||
return query.SessionColumnCreationDate
|
||||
case session.SessionFieldName_SESSION_FIELD_NAME_UNSPECIFIED:
|
||||
// Handle all remaining cases so the linter succeeds
|
||||
return query.Column{}
|
||||
default:
|
||||
return query.Column{}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) createSessionRequestToCommand(ctx context.Context, req *session.CreateSessionRequest) ([]command.SessionCommand, map[string][]byte, *domain.UserAgent, time.Duration, error) {
|
||||
checks, err := s.checksToCommand(ctx, req.Checks)
|
||||
if err != nil {
|
||||
return nil, nil, nil, 0, err
|
||||
}
|
||||
return checks, req.GetMetadata(), userAgentToCommand(req.GetUserAgent()), req.GetLifetime().AsDuration(), nil
|
||||
}
|
||||
|
||||
func userAgentToCommand(userAgent *session.UserAgent) *domain.UserAgent {
|
||||
if userAgent == nil {
|
||||
return nil
|
||||
}
|
||||
out := &domain.UserAgent{
|
||||
FingerprintID: userAgent.FingerprintId,
|
||||
IP: net.ParseIP(userAgent.GetIp()),
|
||||
Description: userAgent.Description,
|
||||
}
|
||||
if len(userAgent.Header) > 0 {
|
||||
out.Header = make(http.Header, len(userAgent.Header))
|
||||
for k, values := range userAgent.Header {
|
||||
out.Header[k] = values.GetValues()
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *Server) setSessionRequestToCommand(ctx context.Context, req *session.SetSessionRequest) ([]command.SessionCommand, error) {
|
||||
checks, err := s.checksToCommand(ctx, req.Checks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return checks, nil
|
||||
}
|
||||
|
||||
func (s *Server) checksToCommand(ctx context.Context, checks *session.Checks) ([]command.SessionCommand, error) {
|
||||
checkUser, err := userCheck(checks.GetUser())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sessionChecks := make([]command.SessionCommand, 0, 7)
|
||||
if checkUser != nil {
|
||||
user, err := checkUser.search(ctx, s.query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !user.State.IsEnabled() {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "SESSION-Gj4ko", "Errors.User.NotActive")
|
||||
}
|
||||
|
||||
var preferredLanguage *language.Tag
|
||||
if user.Human != nil && !user.Human.PreferredLanguage.IsRoot() {
|
||||
preferredLanguage = &user.Human.PreferredLanguage
|
||||
}
|
||||
sessionChecks = append(sessionChecks, command.CheckUser(user.ID, user.ResourceOwner, preferredLanguage))
|
||||
}
|
||||
if password := checks.GetPassword(); password != nil {
|
||||
sessionChecks = append(sessionChecks, command.CheckPassword(password.GetPassword()))
|
||||
}
|
||||
if intent := checks.GetIdpIntent(); intent != nil {
|
||||
sessionChecks = append(sessionChecks, command.CheckIntent(intent.GetIdpIntentId(), intent.GetIdpIntentToken()))
|
||||
}
|
||||
if passkey := checks.GetWebAuthN(); passkey != nil {
|
||||
sessionChecks = append(sessionChecks, s.command.CheckWebAuthN(passkey.GetCredentialAssertionData()))
|
||||
}
|
||||
if totp := checks.GetTotp(); totp != nil {
|
||||
sessionChecks = append(sessionChecks, command.CheckTOTP(totp.GetCode()))
|
||||
}
|
||||
if otp := checks.GetOtpSms(); otp != nil {
|
||||
sessionChecks = append(sessionChecks, command.CheckOTPSMS(otp.GetCode()))
|
||||
}
|
||||
if otp := checks.GetOtpEmail(); otp != nil {
|
||||
sessionChecks = append(sessionChecks, command.CheckOTPEmail(otp.GetCode()))
|
||||
}
|
||||
return sessionChecks, nil
|
||||
}
|
||||
|
||||
func (s *Server) challengesToCommand(challenges *session.RequestChallenges, cmds []command.SessionCommand) (*session.Challenges, []command.SessionCommand, error) {
|
||||
if challenges == nil {
|
||||
return nil, cmds, nil
|
||||
}
|
||||
resp := new(session.Challenges)
|
||||
if req := challenges.GetWebAuthN(); req != nil {
|
||||
challenge, cmd := s.createWebAuthNChallengeCommand(req)
|
||||
resp.WebAuthN = challenge
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
if req := challenges.GetOtpSms(); req != nil {
|
||||
challenge, cmd := s.createOTPSMSChallengeCommand(req)
|
||||
resp.OtpSms = challenge
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
if req := challenges.GetOtpEmail(); req != nil {
|
||||
challenge, cmd, err := s.createOTPEmailChallengeCommand(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
resp.OtpEmail = challenge
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
return resp, cmds, nil
|
||||
}
|
||||
|
||||
func (s *Server) createWebAuthNChallengeCommand(req *session.RequestChallenges_WebAuthN) (*session.Challenges_WebAuthN, command.SessionCommand) {
|
||||
challenge := &session.Challenges_WebAuthN{
|
||||
PublicKeyCredentialRequestOptions: new(structpb.Struct),
|
||||
}
|
||||
userVerification := userVerificationRequirementToDomain(req.GetUserVerificationRequirement())
|
||||
return challenge, s.command.CreateWebAuthNChallenge(userVerification, req.GetDomain(), challenge.PublicKeyCredentialRequestOptions)
|
||||
}
|
||||
|
||||
func userVerificationRequirementToDomain(req session.UserVerificationRequirement) domain.UserVerificationRequirement {
|
||||
switch req {
|
||||
case session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_UNSPECIFIED:
|
||||
return domain.UserVerificationRequirementUnspecified
|
||||
case session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_REQUIRED:
|
||||
return domain.UserVerificationRequirementRequired
|
||||
case session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_PREFERRED:
|
||||
return domain.UserVerificationRequirementPreferred
|
||||
case session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_DISCOURAGED:
|
||||
return domain.UserVerificationRequirementDiscouraged
|
||||
default:
|
||||
return domain.UserVerificationRequirementUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) createOTPSMSChallengeCommand(req *session.RequestChallenges_OTPSMS) (*string, command.SessionCommand) {
|
||||
if req.GetReturnCode() {
|
||||
challenge := new(string)
|
||||
return challenge, s.command.CreateOTPSMSChallengeReturnCode(challenge)
|
||||
}
|
||||
|
||||
return nil, s.command.CreateOTPSMSChallenge()
|
||||
|
||||
}
|
||||
|
||||
func (s *Server) createOTPEmailChallengeCommand(req *session.RequestChallenges_OTPEmail) (*string, command.SessionCommand, error) {
|
||||
switch t := req.GetDeliveryType().(type) {
|
||||
case *session.RequestChallenges_OTPEmail_SendCode_:
|
||||
cmd, err := s.command.CreateOTPEmailChallengeURLTemplate(t.SendCode.GetUrlTemplate())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, cmd, nil
|
||||
case *session.RequestChallenges_OTPEmail_ReturnCode_:
|
||||
challenge := new(string)
|
||||
return challenge, s.command.CreateOTPEmailChallengeReturnCode(challenge), nil
|
||||
case nil:
|
||||
return nil, s.command.CreateOTPEmailChallenge(), nil
|
||||
default:
|
||||
return nil, nil, zerrors.ThrowUnimplementedf(nil, "SESSION-k3ng0", "delivery_type oneOf %T in OTPEmailChallenge not implemented", t)
|
||||
}
|
||||
}
|
||||
|
||||
func userCheck(user *session.CheckUser) (userSearch, error) {
|
||||
if user == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch s := user.GetSearch().(type) {
|
||||
case *session.CheckUser_UserId:
|
||||
return userByID(s.UserId), nil
|
||||
case *session.CheckUser_LoginName:
|
||||
return userByLoginName(s.LoginName)
|
||||
default:
|
||||
return nil, zerrors.ThrowUnimplementedf(nil, "SESSION-d3b4g0", "user search %T not implemented", s)
|
||||
}
|
||||
}
|
||||
|
||||
type userSearch interface {
|
||||
search(ctx context.Context, q *query.Queries) (*query.User, error)
|
||||
}
|
||||
|
||||
func userByID(userID string) userSearch {
|
||||
return userSearchByID{userID}
|
||||
}
|
||||
|
||||
func userByLoginName(loginName string) (userSearch, error) {
|
||||
return userSearchByLoginName{loginName}, nil
|
||||
}
|
||||
|
||||
type userSearchByID struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func (u userSearchByID) search(ctx context.Context, q *query.Queries) (*query.User, error) {
|
||||
return q.GetUserByID(ctx, true, u.id)
|
||||
}
|
||||
|
||||
type userSearchByLoginName struct {
|
||||
loginName string
|
||||
}
|
||||
|
||||
func (u userSearchByLoginName) search(ctx context.Context, q *query.Queries) (*query.User, error) {
|
||||
return q.GetUserByLoginName(ctx, true, u.loginName)
|
||||
}
|
996
internal/api/grpc/session/v2beta/session_integration_test.go
Normal file
996
internal/api/grpc/session/v2beta/session_integration_test.go
Normal file
@@ -0,0 +1,996 @@
|
||||
//go:build integration
|
||||
|
||||
package session_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/logging"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
CTX context.Context
|
||||
IAMOwnerCTX context.Context
|
||||
Tester *integration.Tester
|
||||
Client session.SessionServiceClient
|
||||
User *user.AddHumanUserResponse
|
||||
DeactivatedUser *user.AddHumanUserResponse
|
||||
LockedUser *user.AddHumanUserResponse
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, errCtx, cancel := integration.Contexts(5 * time.Minute)
|
||||
defer cancel()
|
||||
|
||||
Tester = integration.NewTester(ctx)
|
||||
defer Tester.Done()
|
||||
Client = Tester.Client.SessionV2beta
|
||||
|
||||
CTX, _ = Tester.WithAuthorization(ctx, integration.OrgOwner), errCtx
|
||||
IAMOwnerCTX = Tester.WithAuthorization(ctx, integration.IAMOwner)
|
||||
User = createFullUser(CTX)
|
||||
DeactivatedUser = createDeactivatedUser(CTX)
|
||||
LockedUser = createLockedUser(CTX)
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func createFullUser(ctx context.Context) *user.AddHumanUserResponse {
|
||||
userResp := Tester.CreateHumanUser(ctx)
|
||||
Tester.Client.UserV2.VerifyEmail(ctx, &user.VerifyEmailRequest{
|
||||
UserId: userResp.GetUserId(),
|
||||
VerificationCode: userResp.GetEmailCode(),
|
||||
})
|
||||
Tester.Client.UserV2.VerifyPhone(ctx, &user.VerifyPhoneRequest{
|
||||
UserId: userResp.GetUserId(),
|
||||
VerificationCode: userResp.GetPhoneCode(),
|
||||
})
|
||||
Tester.SetUserPassword(ctx, userResp.GetUserId(), integration.UserPassword, false)
|
||||
Tester.RegisterUserPasskey(ctx, userResp.GetUserId())
|
||||
return userResp
|
||||
}
|
||||
|
||||
func createDeactivatedUser(ctx context.Context) *user.AddHumanUserResponse {
|
||||
userResp := Tester.CreateHumanUser(ctx)
|
||||
_, err := Tester.Client.UserV2.DeactivateUser(ctx, &user.DeactivateUserRequest{UserId: userResp.GetUserId()})
|
||||
logging.OnError(err).Fatal("deactivate human user")
|
||||
return userResp
|
||||
}
|
||||
|
||||
func createLockedUser(ctx context.Context) *user.AddHumanUserResponse {
|
||||
userResp := Tester.CreateHumanUser(ctx)
|
||||
_, err := Tester.Client.UserV2.LockUser(ctx, &user.LockUserRequest{UserId: userResp.GetUserId()})
|
||||
logging.OnError(err).Fatal("lock human user")
|
||||
return userResp
|
||||
}
|
||||
|
||||
func verifyCurrentSession(t testing.TB, id, token string, sequence uint64, window time.Duration, metadata map[string][]byte, userAgent *session.UserAgent, expirationWindow time.Duration, userID string, factors ...wantFactor) *session.Session {
|
||||
t.Helper()
|
||||
require.NotEmpty(t, id)
|
||||
require.NotEmpty(t, token)
|
||||
|
||||
resp, err := Client.GetSession(CTX, &session.GetSessionRequest{
|
||||
SessionId: id,
|
||||
SessionToken: &token,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s := resp.GetSession()
|
||||
|
||||
assert.Equal(t, id, s.GetId())
|
||||
assert.WithinRange(t, s.GetCreationDate().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
||||
assert.WithinRange(t, s.GetChangeDate().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
||||
assert.Equal(t, sequence, s.GetSequence())
|
||||
assert.Equal(t, metadata, s.GetMetadata())
|
||||
|
||||
if !proto.Equal(userAgent, s.GetUserAgent()) {
|
||||
t.Errorf("user agent =\n%v\nwant\n%v", s.GetUserAgent(), userAgent)
|
||||
}
|
||||
if expirationWindow == 0 {
|
||||
assert.Nil(t, s.GetExpirationDate())
|
||||
} else {
|
||||
assert.WithinRange(t, s.GetExpirationDate().AsTime(), time.Now().Add(-expirationWindow), time.Now().Add(expirationWindow))
|
||||
}
|
||||
|
||||
verifyFactors(t, s.GetFactors(), window, userID, factors)
|
||||
return s
|
||||
}
|
||||
|
||||
type wantFactor int
|
||||
|
||||
const (
|
||||
wantUserFactor wantFactor = iota
|
||||
wantPasswordFactor
|
||||
wantWebAuthNFactor
|
||||
wantWebAuthNFactorUserVerified
|
||||
wantTOTPFactor
|
||||
wantIntentFactor
|
||||
wantOTPSMSFactor
|
||||
wantOTPEmailFactor
|
||||
)
|
||||
|
||||
func verifyFactors(t testing.TB, factors *session.Factors, window time.Duration, userID string, want []wantFactor) {
|
||||
for _, w := range want {
|
||||
switch w {
|
||||
case wantUserFactor:
|
||||
uf := factors.GetUser()
|
||||
assert.NotNil(t, uf)
|
||||
assert.WithinRange(t, uf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
||||
assert.Equal(t, userID, uf.GetId())
|
||||
case wantPasswordFactor:
|
||||
pf := factors.GetPassword()
|
||||
assert.NotNil(t, pf)
|
||||
assert.WithinRange(t, pf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
||||
case wantWebAuthNFactor:
|
||||
pf := factors.GetWebAuthN()
|
||||
assert.NotNil(t, pf)
|
||||
assert.WithinRange(t, pf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
||||
assert.False(t, pf.GetUserVerified())
|
||||
case wantWebAuthNFactorUserVerified:
|
||||
pf := factors.GetWebAuthN()
|
||||
assert.NotNil(t, pf)
|
||||
assert.WithinRange(t, pf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
||||
assert.True(t, pf.GetUserVerified())
|
||||
case wantTOTPFactor:
|
||||
pf := factors.GetTotp()
|
||||
assert.NotNil(t, pf)
|
||||
assert.WithinRange(t, pf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
||||
case wantIntentFactor:
|
||||
pf := factors.GetIntent()
|
||||
assert.NotNil(t, pf)
|
||||
assert.WithinRange(t, pf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
||||
case wantOTPSMSFactor:
|
||||
pf := factors.GetOtpSms()
|
||||
assert.NotNil(t, pf)
|
||||
assert.WithinRange(t, pf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
||||
case wantOTPEmailFactor:
|
||||
pf := factors.GetOtpEmail()
|
||||
assert.NotNil(t, pf)
|
||||
assert.WithinRange(t, pf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_CreateSession(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
req *session.CreateSessionRequest
|
||||
want *session.CreateSessionResponse
|
||||
wantErr bool
|
||||
wantFactors []wantFactor
|
||||
wantUserAgent *session.UserAgent
|
||||
wantExpirationWindow time.Duration
|
||||
}{
|
||||
{
|
||||
name: "empty session",
|
||||
req: &session.CreateSessionRequest{
|
||||
Metadata: map[string][]byte{"foo": []byte("bar")},
|
||||
},
|
||||
want: &session.CreateSessionResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user agent",
|
||||
req: &session.CreateSessionRequest{
|
||||
Metadata: map[string][]byte{"foo": []byte("bar")},
|
||||
UserAgent: &session.UserAgent{
|
||||
FingerprintId: gu.Ptr("fingerPrintID"),
|
||||
Ip: gu.Ptr("1.2.3.4"),
|
||||
Description: gu.Ptr("Description"),
|
||||
Header: map[string]*session.UserAgent_HeaderValues{
|
||||
"foo": {Values: []string{"foo", "bar"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &session.CreateSessionResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
wantUserAgent: &session.UserAgent{
|
||||
FingerprintId: gu.Ptr("fingerPrintID"),
|
||||
Ip: gu.Ptr("1.2.3.4"),
|
||||
Description: gu.Ptr("Description"),
|
||||
Header: map[string]*session.UserAgent_HeaderValues{
|
||||
"foo": {Values: []string{"foo", "bar"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "negative lifetime",
|
||||
req: &session.CreateSessionRequest{
|
||||
Metadata: map[string][]byte{"foo": []byte("bar")},
|
||||
Lifetime: durationpb.New(-5 * time.Minute),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "lifetime",
|
||||
req: &session.CreateSessionRequest{
|
||||
Metadata: map[string][]byte{"foo": []byte("bar")},
|
||||
Lifetime: durationpb.New(5 * time.Minute),
|
||||
},
|
||||
want: &session.CreateSessionResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
wantExpirationWindow: 5 * time.Minute,
|
||||
},
|
||||
{
|
||||
name: "with user",
|
||||
req: &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: User.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
Metadata: map[string][]byte{"foo": []byte("bar")},
|
||||
},
|
||||
want: &session.CreateSessionResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
wantFactors: []wantFactor{wantUserFactor},
|
||||
},
|
||||
{
|
||||
name: "deactivated user",
|
||||
req: &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: DeactivatedUser.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "locked user",
|
||||
req: &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: LockedUser.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "password without user error",
|
||||
req: &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
Password: &session.CheckPassword{
|
||||
Password: "Difficult",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "passkey without user error",
|
||||
req: &session.CreateSessionRequest{
|
||||
Challenges: &session.RequestChallenges{
|
||||
WebAuthN: &session.RequestChallenges_WebAuthN{
|
||||
Domain: Tester.Config.ExternalDomain,
|
||||
UserVerificationRequirement: session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_REQUIRED,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "passkey without domain (not registered) error",
|
||||
req: &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: User.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
Challenges: &session.RequestChallenges{
|
||||
WebAuthN: &session.RequestChallenges_WebAuthN{
|
||||
UserVerificationRequirement: session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_REQUIRED,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Client.CreateSession(CTX, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
|
||||
verifyCurrentSession(t, got.GetSessionId(), got.GetSessionToken(), got.GetDetails().GetSequence(), time.Minute, tt.req.GetMetadata(), tt.wantUserAgent, tt.wantExpirationWindow, User.GetUserId(), tt.wantFactors...)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_CreateSession_lock_user(t *testing.T) {
|
||||
// create a separate org so we don't interfere with any other test
|
||||
org := Tester.CreateOrganization(IAMOwnerCTX,
|
||||
fmt.Sprintf("TestServer_CreateSession_lock_user_%d", time.Now().UnixNano()),
|
||||
fmt.Sprintf("%d@mouse.com", time.Now().UnixNano()),
|
||||
)
|
||||
userID := org.CreatedAdmins[0].GetUserId()
|
||||
Tester.SetUserPassword(IAMOwnerCTX, userID, integration.UserPassword, false)
|
||||
|
||||
// enable password lockout
|
||||
maxAttempts := 2
|
||||
ctxOrg := metadata.AppendToOutgoingContext(IAMOwnerCTX, "x-zitadel-orgid", org.GetOrganizationId())
|
||||
_, err := Tester.Client.Mgmt.AddCustomLockoutPolicy(ctxOrg, &mgmt.AddCustomLockoutPolicyRequest{
|
||||
MaxPasswordAttempts: uint32(maxAttempts),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := 0; i <= maxAttempts; i++ {
|
||||
_, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: userID,
|
||||
},
|
||||
},
|
||||
Password: &session.CheckPassword{
|
||||
Password: "invalid",
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
statusCode := status.Code(err)
|
||||
expectedCode := codes.InvalidArgument
|
||||
// as soon as we hit the limit the user is locked and following request will
|
||||
// already deny any check with a precondition failed since the user is locked
|
||||
if i >= maxAttempts {
|
||||
expectedCode = codes.FailedPrecondition
|
||||
}
|
||||
assert.Equal(t, expectedCode, statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_CreateSession_webauthn(t *testing.T) {
|
||||
// create new session with user and request the webauthn challenge
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: User.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
Challenges: &session.RequestChallenges{
|
||||
WebAuthN: &session.RequestChallenges_WebAuthN{
|
||||
Domain: Tester.Config.ExternalDomain,
|
||||
UserVerificationRequirement: session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_REQUIRED,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
|
||||
assertionData, err := Tester.WebAuthN.CreateAssertionResponse(createResp.GetChallenges().GetWebAuthN().GetPublicKeyCredentialRequestOptions(), true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// update the session with webauthn assertion data
|
||||
updateResp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
WebAuthN: &session.CheckWebAuthN{
|
||||
CredentialAssertionData: assertionData,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId(), wantUserFactor, wantWebAuthNFactorUserVerified)
|
||||
}
|
||||
|
||||
func TestServer_CreateSession_successfulIntent(t *testing.T) {
|
||||
idpID := Tester.AddGenericOAuthProvider(t, CTX)
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: User.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
|
||||
intentID, token, _, _ := Tester.CreateSuccessfulOAuthIntent(t, CTX, idpID, User.GetUserId(), "id")
|
||||
updateResp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
IdpIntent: &session.CheckIDPIntent{
|
||||
IdpIntentId: intentID,
|
||||
IdpIntentToken: token,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId(), wantUserFactor, wantIntentFactor)
|
||||
}
|
||||
|
||||
func TestServer_CreateSession_successfulIntent_instant(t *testing.T) {
|
||||
idpID := Tester.AddGenericOAuthProvider(t, CTX)
|
||||
|
||||
intentID, token, _, _ := Tester.CreateSuccessfulOAuthIntent(t, CTX, idpID, User.GetUserId(), "id")
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: User.GetUserId(),
|
||||
},
|
||||
},
|
||||
IdpIntent: &session.CheckIDPIntent{
|
||||
IdpIntentId: intentID,
|
||||
IdpIntentToken: token,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId(), wantUserFactor, wantIntentFactor)
|
||||
}
|
||||
|
||||
func TestServer_CreateSession_successfulIntentUnknownUserID(t *testing.T) {
|
||||
idpID := Tester.AddGenericOAuthProvider(t, CTX)
|
||||
|
||||
// successful intent without known / linked user
|
||||
idpUserID := "id"
|
||||
intentID, token, _, _ := Tester.CreateSuccessfulOAuthIntent(t, CTX, idpID, "", idpUserID)
|
||||
|
||||
// link the user (with info from intent)
|
||||
Tester.CreateUserIDPlink(CTX, User.GetUserId(), idpUserID, idpID, User.GetUserId())
|
||||
|
||||
// session with intent check must now succeed
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: User.GetUserId(),
|
||||
},
|
||||
},
|
||||
IdpIntent: &session.CheckIDPIntent{
|
||||
IdpIntentId: intentID,
|
||||
IdpIntentToken: token,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId(), wantUserFactor, wantIntentFactor)
|
||||
}
|
||||
|
||||
func TestServer_CreateSession_startedIntentFalseToken(t *testing.T) {
|
||||
idpID := Tester.AddGenericOAuthProvider(t, CTX)
|
||||
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: User.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
|
||||
intentID := Tester.CreateIntent(t, CTX, idpID)
|
||||
_, err = Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
IdpIntent: &session.CheckIDPIntent{
|
||||
IdpIntentId: intentID,
|
||||
IdpIntentToken: "false",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func registerTOTP(ctx context.Context, t *testing.T, userID string) (secret string) {
|
||||
resp, err := Tester.Client.UserV2.RegisterTOTP(ctx, &user.RegisterTOTPRequest{
|
||||
UserId: userID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
secret = resp.GetSecret()
|
||||
code, err := totp.GenerateCode(secret, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = Tester.Client.UserV2.VerifyTOTPRegistration(ctx, &user.VerifyTOTPRegistrationRequest{
|
||||
UserId: userID,
|
||||
Code: code,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return secret
|
||||
}
|
||||
|
||||
func registerOTPSMS(ctx context.Context, t *testing.T, userID string) {
|
||||
_, err := Tester.Client.UserV2.AddOTPSMS(ctx, &user.AddOTPSMSRequest{
|
||||
UserId: userID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func registerOTPEmail(ctx context.Context, t *testing.T, userID string) {
|
||||
_, err := Tester.Client.UserV2.AddOTPEmail(ctx, &user.AddOTPEmailRequest{
|
||||
UserId: userID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestServer_SetSession_flow_totp(t *testing.T) {
|
||||
userExisting := createFullUser(CTX)
|
||||
|
||||
// create new, empty session
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{})
|
||||
require.NoError(t, err)
|
||||
sessionToken := createResp.GetSessionToken()
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, "")
|
||||
|
||||
t.Run("check user", func(t *testing.T) {
|
||||
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: userExisting.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionToken = resp.GetSessionToken()
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, userExisting.GetUserId(), wantUserFactor)
|
||||
})
|
||||
|
||||
t.Run("check webauthn, user verified (passkey)", func(t *testing.T) {
|
||||
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Challenges: &session.RequestChallenges{
|
||||
WebAuthN: &session.RequestChallenges_WebAuthN{
|
||||
Domain: Tester.Config.ExternalDomain,
|
||||
UserVerificationRequirement: session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_REQUIRED,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, userExisting.GetUserId())
|
||||
sessionToken = resp.GetSessionToken()
|
||||
|
||||
assertionData, err := Tester.WebAuthN.CreateAssertionResponse(resp.GetChallenges().GetWebAuthN().GetPublicKeyCredentialRequestOptions(), true)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err = Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
WebAuthN: &session.CheckWebAuthN{
|
||||
CredentialAssertionData: assertionData,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionToken = resp.GetSessionToken()
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, userExisting.GetUserId(), wantUserFactor, wantWebAuthNFactorUserVerified)
|
||||
})
|
||||
|
||||
userAuthCtx := Tester.WithAuthorizationToken(CTX, sessionToken)
|
||||
Tester.RegisterUserU2F(userAuthCtx, userExisting.GetUserId())
|
||||
totpSecret := registerTOTP(userAuthCtx, t, userExisting.GetUserId())
|
||||
registerOTPSMS(userAuthCtx, t, userExisting.GetUserId())
|
||||
registerOTPEmail(userAuthCtx, t, userExisting.GetUserId())
|
||||
|
||||
t.Run("check TOTP", func(t *testing.T) {
|
||||
code, err := totp.GenerateCode(totpSecret, time.Now())
|
||||
require.NoError(t, err)
|
||||
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
Totp: &session.CheckTOTP{
|
||||
Code: code,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionToken = resp.GetSessionToken()
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, userExisting.GetUserId(), wantUserFactor, wantTOTPFactor)
|
||||
})
|
||||
|
||||
userImport := Tester.CreateHumanUserWithTOTP(CTX, totpSecret)
|
||||
createRespImport, err := Client.CreateSession(CTX, &session.CreateSessionRequest{})
|
||||
require.NoError(t, err)
|
||||
sessionTokenImport := createRespImport.GetSessionToken()
|
||||
verifyCurrentSession(t, createRespImport.GetSessionId(), sessionTokenImport, createRespImport.GetDetails().GetSequence(), time.Minute, nil, nil, 0, "")
|
||||
|
||||
t.Run("check user", func(t *testing.T) {
|
||||
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createRespImport.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: userImport.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionTokenImport = resp.GetSessionToken()
|
||||
verifyCurrentSession(t, createRespImport.GetSessionId(), sessionTokenImport, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, userImport.GetUserId(), wantUserFactor)
|
||||
})
|
||||
t.Run("check TOTP", func(t *testing.T) {
|
||||
code, err := totp.GenerateCode(totpSecret, time.Now())
|
||||
require.NoError(t, err)
|
||||
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createRespImport.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
Totp: &session.CheckTOTP{
|
||||
Code: code,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionTokenImport = resp.GetSessionToken()
|
||||
verifyCurrentSession(t, createRespImport.GetSessionId(), sessionTokenImport, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, userImport.GetUserId(), wantUserFactor, wantTOTPFactor)
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_SetSession_flow(t *testing.T) {
|
||||
// create new, empty session
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{})
|
||||
require.NoError(t, err)
|
||||
sessionToken := createResp.GetSessionToken()
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
|
||||
t.Run("check user", func(t *testing.T) {
|
||||
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: User.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionToken = resp.GetSessionToken()
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId(), wantUserFactor)
|
||||
})
|
||||
|
||||
t.Run("check webauthn, user verified (passkey)", func(t *testing.T) {
|
||||
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Challenges: &session.RequestChallenges{
|
||||
WebAuthN: &session.RequestChallenges_WebAuthN{
|
||||
Domain: Tester.Config.ExternalDomain,
|
||||
UserVerificationRequirement: session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_REQUIRED,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
sessionToken = resp.GetSessionToken()
|
||||
|
||||
assertionData, err := Tester.WebAuthN.CreateAssertionResponse(resp.GetChallenges().GetWebAuthN().GetPublicKeyCredentialRequestOptions(), true)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err = Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
WebAuthN: &session.CheckWebAuthN{
|
||||
CredentialAssertionData: assertionData,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionToken = resp.GetSessionToken()
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId(), wantUserFactor, wantWebAuthNFactorUserVerified)
|
||||
})
|
||||
|
||||
userAuthCtx := Tester.WithAuthorizationToken(CTX, sessionToken)
|
||||
Tester.RegisterUserU2F(userAuthCtx, User.GetUserId())
|
||||
totpSecret := registerTOTP(userAuthCtx, t, User.GetUserId())
|
||||
registerOTPSMS(userAuthCtx, t, User.GetUserId())
|
||||
registerOTPEmail(userAuthCtx, t, User.GetUserId())
|
||||
|
||||
t.Run("check webauthn, user not verified (U2F)", func(t *testing.T) {
|
||||
|
||||
for _, userVerificationRequirement := range []session.UserVerificationRequirement{
|
||||
session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_PREFERRED,
|
||||
session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_DISCOURAGED,
|
||||
} {
|
||||
t.Run(userVerificationRequirement.String(), func(t *testing.T) {
|
||||
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Challenges: &session.RequestChallenges{
|
||||
WebAuthN: &session.RequestChallenges_WebAuthN{
|
||||
Domain: Tester.Config.ExternalDomain,
|
||||
UserVerificationRequirement: userVerificationRequirement,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
sessionToken = resp.GetSessionToken()
|
||||
|
||||
assertionData, err := Tester.WebAuthN.CreateAssertionResponse(resp.GetChallenges().GetWebAuthN().GetPublicKeyCredentialRequestOptions(), false)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err = Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
WebAuthN: &session.CheckWebAuthN{
|
||||
CredentialAssertionData: assertionData,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionToken = resp.GetSessionToken()
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId(), wantUserFactor, wantWebAuthNFactor)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("check TOTP", func(t *testing.T) {
|
||||
code, err := totp.GenerateCode(totpSecret, time.Now())
|
||||
require.NoError(t, err)
|
||||
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
Totp: &session.CheckTOTP{
|
||||
Code: code,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionToken = resp.GetSessionToken()
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId(), wantUserFactor, wantWebAuthNFactor, wantTOTPFactor)
|
||||
})
|
||||
|
||||
t.Run("check OTP SMS", func(t *testing.T) {
|
||||
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Challenges: &session.RequestChallenges{
|
||||
OtpSms: &session.RequestChallenges_OTPSMS{ReturnCode: true},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
sessionToken = resp.GetSessionToken()
|
||||
|
||||
otp := resp.GetChallenges().GetOtpSms()
|
||||
require.NotEmpty(t, otp)
|
||||
|
||||
resp, err = Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
OtpSms: &session.CheckOTP{
|
||||
Code: otp,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionToken = resp.GetSessionToken()
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId(), wantUserFactor, wantWebAuthNFactor, wantOTPSMSFactor)
|
||||
})
|
||||
|
||||
t.Run("check OTP Email", func(t *testing.T) {
|
||||
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Challenges: &session.RequestChallenges{
|
||||
OtpEmail: &session.RequestChallenges_OTPEmail{
|
||||
DeliveryType: &session.RequestChallenges_OTPEmail_ReturnCode_{},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
sessionToken = resp.GetSessionToken()
|
||||
|
||||
otp := resp.GetChallenges().GetOtpEmail()
|
||||
require.NotEmpty(t, otp)
|
||||
|
||||
resp, err = Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
OtpEmail: &session.CheckOTP{
|
||||
Code: otp,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionToken = resp.GetSessionToken()
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId(), wantUserFactor, wantWebAuthNFactor, wantOTPEmailFactor)
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_SetSession_expired(t *testing.T) {
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Lifetime: durationpb.New(20 * time.Second),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// test session token works
|
||||
_, err = Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Lifetime: durationpb.New(20 * time.Second),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// ensure session expires and does not work anymore
|
||||
time.Sleep(20 * time.Second)
|
||||
_, err = Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Lifetime: durationpb.New(20 * time.Second),
|
||||
})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestServer_DeleteSession_token(t *testing.T) {
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = Client.DeleteSession(CTX, &session.DeleteSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
SessionToken: gu.Ptr("invalid"),
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = Client.DeleteSession(CTX, &session.DeleteSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
SessionToken: gu.Ptr(createResp.GetSessionToken()),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestServer_DeleteSession_own_session(t *testing.T) {
|
||||
// create two users for the test and a session each to get tokens for authorization
|
||||
user1 := Tester.CreateHumanUser(CTX)
|
||||
Tester.SetUserPassword(CTX, user1.GetUserId(), integration.UserPassword, false)
|
||||
_, token1, _, _ := Tester.CreatePasswordSession(t, CTX, user1.GetUserId(), integration.UserPassword)
|
||||
|
||||
user2 := Tester.CreateHumanUser(CTX)
|
||||
Tester.SetUserPassword(CTX, user2.GetUserId(), integration.UserPassword, false)
|
||||
_, token2, _, _ := Tester.CreatePasswordSession(t, CTX, user2.GetUserId(), integration.UserPassword)
|
||||
|
||||
// create a new session for the first user
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: user1.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// delete the new (user1) session must not be possible with user (has no permission)
|
||||
_, err = Client.DeleteSession(Tester.WithAuthorizationToken(context.Background(), token2), &session.DeleteSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
// delete the new (user1) session by themselves
|
||||
_, err = Client.DeleteSession(Tester.WithAuthorizationToken(context.Background(), token1), &session.DeleteSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestServer_DeleteSession_with_permission(t *testing.T) {
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: User.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// delete the new session by ORG_OWNER
|
||||
_, err = Client.DeleteSession(Tester.WithAuthorization(context.Background(), integration.OrgOwner), &session.DeleteSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_ZITADEL_API_missing_authentication(t *testing.T) {
|
||||
// create new, empty session
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := metadata.AppendToOutgoingContext(context.Background(), "Authorization", fmt.Sprintf("Bearer %s", createResp.GetSessionToken()))
|
||||
sessionResp, err := Client.GetSession(ctx, &session.GetSessionRequest{SessionId: createResp.GetSessionId()})
|
||||
require.Error(t, err)
|
||||
require.Nil(t, sessionResp)
|
||||
}
|
||||
|
||||
func Test_ZITADEL_API_success(t *testing.T) {
|
||||
id, token, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, User.GetUserId())
|
||||
|
||||
ctx := Tester.WithAuthorizationToken(context.Background(), token)
|
||||
sessionResp, err := Client.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
require.NoError(t, err)
|
||||
|
||||
webAuthN := sessionResp.GetSession().GetFactors().GetWebAuthN()
|
||||
require.NotNil(t, id, webAuthN.GetVerifiedAt().AsTime())
|
||||
require.True(t, webAuthN.GetUserVerified())
|
||||
}
|
||||
|
||||
func Test_ZITADEL_API_session_not_found(t *testing.T) {
|
||||
id, token, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, User.GetUserId())
|
||||
|
||||
// test session token works
|
||||
ctx := Tester.WithAuthorizationToken(context.Background(), token)
|
||||
_, err := Client.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
require.NoError(t, err)
|
||||
|
||||
//terminate the session and test it does not work anymore
|
||||
_, err = Client.DeleteSession(CTX, &session.DeleteSessionRequest{
|
||||
SessionId: id,
|
||||
SessionToken: gu.Ptr(token),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
ctx = Tester.WithAuthorizationToken(context.Background(), token)
|
||||
_, err = Client.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func Test_ZITADEL_API_session_expired(t *testing.T) {
|
||||
id, token, _, _ := Tester.CreateVerifiedWebAuthNSessionWithLifetime(t, CTX, User.GetUserId(), 20*time.Second)
|
||||
|
||||
// test session token works
|
||||
ctx := Tester.WithAuthorizationToken(context.Background(), token)
|
||||
_, err := Client.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
require.NoError(t, err)
|
||||
|
||||
// ensure session expires and does not work anymore
|
||||
time.Sleep(20 * time.Second)
|
||||
sessionResp, err := Client.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
|
||||
require.Error(t, err)
|
||||
require.Nil(t, sessionResp)
|
||||
}
|
739
internal/api/grpc/session/v2beta/session_test.go
Normal file
739
internal/api/grpc/session/v2beta/session_test.go
Normal file
@@ -0,0 +1,739 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
objpb "github.com/zitadel/zitadel/pkg/grpc/object"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||
)
|
||||
|
||||
var (
|
||||
creationDate = time.Date(2023, 10, 10, 14, 15, 0, 0, time.UTC)
|
||||
)
|
||||
|
||||
func Test_sessionsToPb(t *testing.T) {
|
||||
now := time.Now()
|
||||
past := now.Add(-time.Hour)
|
||||
|
||||
sessions := []*query.Session{
|
||||
{ // no factor, with user agent and expiration
|
||||
ID: "999",
|
||||
CreationDate: now,
|
||||
ChangeDate: now,
|
||||
Sequence: 123,
|
||||
State: domain.SessionStateActive,
|
||||
ResourceOwner: "me",
|
||||
Creator: "he",
|
||||
Metadata: map[string][]byte{"hello": []byte("world")},
|
||||
UserAgent: domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fingerprintID"),
|
||||
Description: gu.Ptr("description"),
|
||||
IP: net.IPv4(1, 2, 3, 4),
|
||||
Header: http.Header{"foo": []string{"foo", "bar"}},
|
||||
},
|
||||
Expiration: now,
|
||||
},
|
||||
{ // user factor
|
||||
ID: "999",
|
||||
CreationDate: now,
|
||||
ChangeDate: now,
|
||||
Sequence: 123,
|
||||
State: domain.SessionStateActive,
|
||||
ResourceOwner: "me",
|
||||
Creator: "he",
|
||||
UserFactor: query.SessionUserFactor{
|
||||
UserID: "345",
|
||||
UserCheckedAt: past,
|
||||
LoginName: "donald",
|
||||
DisplayName: "donald duck",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Metadata: map[string][]byte{"hello": []byte("world")},
|
||||
},
|
||||
{ // password factor
|
||||
ID: "999",
|
||||
CreationDate: now,
|
||||
ChangeDate: now,
|
||||
Sequence: 123,
|
||||
State: domain.SessionStateActive,
|
||||
ResourceOwner: "me",
|
||||
Creator: "he",
|
||||
UserFactor: query.SessionUserFactor{
|
||||
UserID: "345",
|
||||
UserCheckedAt: past,
|
||||
LoginName: "donald",
|
||||
DisplayName: "donald duck",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
PasswordFactor: query.SessionPasswordFactor{
|
||||
PasswordCheckedAt: past,
|
||||
},
|
||||
Metadata: map[string][]byte{"hello": []byte("world")},
|
||||
},
|
||||
{ // webAuthN factor
|
||||
ID: "999",
|
||||
CreationDate: now,
|
||||
ChangeDate: now,
|
||||
Sequence: 123,
|
||||
State: domain.SessionStateActive,
|
||||
ResourceOwner: "me",
|
||||
Creator: "he",
|
||||
UserFactor: query.SessionUserFactor{
|
||||
UserID: "345",
|
||||
UserCheckedAt: past,
|
||||
LoginName: "donald",
|
||||
DisplayName: "donald duck",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
WebAuthNFactor: query.SessionWebAuthNFactor{
|
||||
WebAuthNCheckedAt: past,
|
||||
UserVerified: true,
|
||||
},
|
||||
Metadata: map[string][]byte{"hello": []byte("world")},
|
||||
},
|
||||
{ // totp factor
|
||||
ID: "999",
|
||||
CreationDate: now,
|
||||
ChangeDate: now,
|
||||
Sequence: 123,
|
||||
State: domain.SessionStateActive,
|
||||
ResourceOwner: "me",
|
||||
Creator: "he",
|
||||
UserFactor: query.SessionUserFactor{
|
||||
UserID: "345",
|
||||
UserCheckedAt: past,
|
||||
LoginName: "donald",
|
||||
DisplayName: "donald duck",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
TOTPFactor: query.SessionTOTPFactor{
|
||||
TOTPCheckedAt: past,
|
||||
},
|
||||
Metadata: map[string][]byte{"hello": []byte("world")},
|
||||
},
|
||||
}
|
||||
|
||||
want := []*session.Session{
|
||||
{ // no factor, with user agent and expiration
|
||||
Id: "999",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
Sequence: 123,
|
||||
Factors: nil,
|
||||
Metadata: map[string][]byte{"hello": []byte("world")},
|
||||
UserAgent: &session.UserAgent{
|
||||
FingerprintId: gu.Ptr("fingerprintID"),
|
||||
Description: gu.Ptr("description"),
|
||||
Ip: gu.Ptr("1.2.3.4"),
|
||||
Header: map[string]*session.UserAgent_HeaderValues{
|
||||
"foo": {Values: []string{"foo", "bar"}},
|
||||
},
|
||||
},
|
||||
ExpirationDate: timestamppb.New(now),
|
||||
},
|
||||
{ // user factor
|
||||
Id: "999",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
Sequence: 123,
|
||||
Factors: &session.Factors{
|
||||
User: &session.UserFactor{
|
||||
VerifiedAt: timestamppb.New(past),
|
||||
Id: "345",
|
||||
LoginName: "donald",
|
||||
DisplayName: "donald duck",
|
||||
OrganizationId: "org1",
|
||||
},
|
||||
},
|
||||
Metadata: map[string][]byte{"hello": []byte("world")},
|
||||
},
|
||||
{ // password factor
|
||||
Id: "999",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
Sequence: 123,
|
||||
Factors: &session.Factors{
|
||||
User: &session.UserFactor{
|
||||
VerifiedAt: timestamppb.New(past),
|
||||
Id: "345",
|
||||
LoginName: "donald",
|
||||
DisplayName: "donald duck",
|
||||
OrganizationId: "org1",
|
||||
},
|
||||
Password: &session.PasswordFactor{
|
||||
VerifiedAt: timestamppb.New(past),
|
||||
},
|
||||
},
|
||||
Metadata: map[string][]byte{"hello": []byte("world")},
|
||||
},
|
||||
{ // webAuthN factor
|
||||
Id: "999",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
Sequence: 123,
|
||||
Factors: &session.Factors{
|
||||
User: &session.UserFactor{
|
||||
VerifiedAt: timestamppb.New(past),
|
||||
Id: "345",
|
||||
LoginName: "donald",
|
||||
DisplayName: "donald duck",
|
||||
OrganizationId: "org1",
|
||||
},
|
||||
WebAuthN: &session.WebAuthNFactor{
|
||||
VerifiedAt: timestamppb.New(past),
|
||||
UserVerified: true,
|
||||
},
|
||||
},
|
||||
Metadata: map[string][]byte{"hello": []byte("world")},
|
||||
},
|
||||
{ // totp factor
|
||||
Id: "999",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
Sequence: 123,
|
||||
Factors: &session.Factors{
|
||||
User: &session.UserFactor{
|
||||
VerifiedAt: timestamppb.New(past),
|
||||
Id: "345",
|
||||
LoginName: "donald",
|
||||
DisplayName: "donald duck",
|
||||
OrganizationId: "org1",
|
||||
},
|
||||
Totp: &session.TOTPFactor{
|
||||
VerifiedAt: timestamppb.New(past),
|
||||
},
|
||||
},
|
||||
Metadata: map[string][]byte{"hello": []byte("world")},
|
||||
},
|
||||
}
|
||||
|
||||
out := sessionsToPb(sessions)
|
||||
require.Len(t, out, len(want))
|
||||
|
||||
for i, got := range out {
|
||||
if !proto.Equal(got, want[i]) {
|
||||
t.Errorf("session %d got:\n%v\nwant:\n%v", i, got, want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_userAgentToPb(t *testing.T) {
|
||||
type args struct {
|
||||
ua domain.UserAgent
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *session.UserAgent
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
args: args{domain.UserAgent{}},
|
||||
},
|
||||
{
|
||||
name: "fingerprint id and description",
|
||||
args: args{domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fingerPrintID"),
|
||||
Description: gu.Ptr("description"),
|
||||
}},
|
||||
want: &session.UserAgent{
|
||||
FingerprintId: gu.Ptr("fingerPrintID"),
|
||||
Description: gu.Ptr("description"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with ip",
|
||||
args: args{domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fingerPrintID"),
|
||||
Description: gu.Ptr("description"),
|
||||
IP: net.IPv4(1, 2, 3, 4),
|
||||
}},
|
||||
want: &session.UserAgent{
|
||||
FingerprintId: gu.Ptr("fingerPrintID"),
|
||||
Description: gu.Ptr("description"),
|
||||
Ip: gu.Ptr("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with header",
|
||||
args: args{domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fingerPrintID"),
|
||||
Description: gu.Ptr("description"),
|
||||
Header: http.Header{
|
||||
"foo": []string{"foo", "bar"},
|
||||
"hello": []string{"world"},
|
||||
},
|
||||
}},
|
||||
want: &session.UserAgent{
|
||||
FingerprintId: gu.Ptr("fingerPrintID"),
|
||||
Description: gu.Ptr("description"),
|
||||
Header: map[string]*session.UserAgent_HeaderValues{
|
||||
"foo": {Values: []string{"foo", "bar"}},
|
||||
"hello": {Values: []string{"world"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := userAgentToPb(tt.args.ua)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustNewTextQuery(t testing.TB, column query.Column, value string, compare query.TextComparison) query.SearchQuery {
|
||||
q, err := query.NewTextQuery(column, value, compare)
|
||||
require.NoError(t, err)
|
||||
return q
|
||||
}
|
||||
|
||||
func mustNewListQuery(t testing.TB, column query.Column, list []any, compare query.ListComparison) query.SearchQuery {
|
||||
q, err := query.NewListQuery(query.SessionColumnID, list, compare)
|
||||
require.NoError(t, err)
|
||||
return q
|
||||
}
|
||||
|
||||
func mustNewTimestampQuery(t testing.TB, column query.Column, ts time.Time, compare query.TimestampComparison) query.SearchQuery {
|
||||
q, err := query.NewTimestampQuery(column, ts, compare)
|
||||
require.NoError(t, err)
|
||||
return q
|
||||
}
|
||||
|
||||
func Test_listSessionsRequestToQuery(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *session.ListSessionsRequest
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *query.SessionsSearchQueries
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "default request",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("123", "456", "789"),
|
||||
req: &session.ListSessionsRequest{},
|
||||
},
|
||||
want: &query.SessionsSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: 0,
|
||||
Limit: 0,
|
||||
Asc: false,
|
||||
},
|
||||
Queries: []query.SearchQuery{
|
||||
mustNewTextQuery(t, query.SessionColumnCreator, "789", query.TextEquals),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default request with sorting column",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("123", "456", "789"),
|
||||
req: &session.ListSessionsRequest{
|
||||
SortingColumn: session.SessionFieldName_SESSION_FIELD_NAME_CREATION_DATE,
|
||||
},
|
||||
},
|
||||
want: &query.SessionsSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: 0,
|
||||
Limit: 0,
|
||||
SortingColumn: query.SessionColumnCreationDate,
|
||||
Asc: false,
|
||||
},
|
||||
Queries: []query.SearchQuery{
|
||||
mustNewTextQuery(t, query.SessionColumnCreator, "789", query.TextEquals),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with list query and sessions",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("123", "456", "789"),
|
||||
req: &session.ListSessionsRequest{
|
||||
Query: &object.ListQuery{
|
||||
Offset: 10,
|
||||
Limit: 20,
|
||||
Asc: true,
|
||||
},
|
||||
Queries: []*session.SearchQuery{
|
||||
{Query: &session.SearchQuery_IdsQuery{
|
||||
IdsQuery: &session.IDsQuery{
|
||||
Ids: []string{"1", "2", "3"},
|
||||
},
|
||||
}},
|
||||
{Query: &session.SearchQuery_IdsQuery{
|
||||
IdsQuery: &session.IDsQuery{
|
||||
Ids: []string{"4", "5", "6"},
|
||||
},
|
||||
}},
|
||||
{Query: &session.SearchQuery_UserIdQuery{
|
||||
UserIdQuery: &session.UserIDQuery{
|
||||
Id: "10",
|
||||
},
|
||||
}},
|
||||
{Query: &session.SearchQuery_CreationDateQuery{
|
||||
CreationDateQuery: &session.CreationDateQuery{
|
||||
CreationDate: timestamppb.New(creationDate),
|
||||
Method: objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_GREATER,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &query.SessionsSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: 10,
|
||||
Limit: 20,
|
||||
Asc: true,
|
||||
},
|
||||
Queries: []query.SearchQuery{
|
||||
mustNewListQuery(t, query.SessionColumnID, []interface{}{"1", "2", "3"}, query.ListIn),
|
||||
mustNewListQuery(t, query.SessionColumnID, []interface{}{"4", "5", "6"}, query.ListIn),
|
||||
mustNewTextQuery(t, query.SessionColumnUserID, "10", query.TextEquals),
|
||||
mustNewTimestampQuery(t, query.SessionColumnCreationDate, creationDate, query.TimestampGreater),
|
||||
mustNewTextQuery(t, query.SessionColumnCreator, "789", query.TextEquals),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid argument error",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("123", "456", "789"),
|
||||
req: &session.ListSessionsRequest{
|
||||
Query: &object.ListQuery{
|
||||
Offset: 10,
|
||||
Limit: 20,
|
||||
Asc: true,
|
||||
},
|
||||
Queries: []*session.SearchQuery{
|
||||
{Query: &session.SearchQuery_IdsQuery{
|
||||
IdsQuery: &session.IDsQuery{
|
||||
Ids: []string{"1", "2", "3"},
|
||||
},
|
||||
}},
|
||||
{Query: nil},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: zerrors.ThrowInvalidArgument(nil, "GRPC-Sfefs", "List.Query.Invalid"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := listSessionsRequestToQuery(tt.args.ctx, tt.args.req)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sessionQueriesToQuery(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
queries []*session.SearchQuery
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []query.SearchQuery
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "creator only",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("123", "456", "789"),
|
||||
},
|
||||
want: []query.SearchQuery{
|
||||
mustNewTextQuery(t, query.SessionColumnCreator, "789", query.TextEquals),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid argument",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("123", "456", "789"),
|
||||
queries: []*session.SearchQuery{
|
||||
{Query: nil},
|
||||
},
|
||||
},
|
||||
wantErr: zerrors.ThrowInvalidArgument(nil, "GRPC-Sfefs", "List.Query.Invalid"),
|
||||
},
|
||||
{
|
||||
name: "creator and sessions",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("123", "456", "789"),
|
||||
queries: []*session.SearchQuery{
|
||||
{Query: &session.SearchQuery_IdsQuery{
|
||||
IdsQuery: &session.IDsQuery{
|
||||
Ids: []string{"1", "2", "3"},
|
||||
},
|
||||
}},
|
||||
{Query: &session.SearchQuery_IdsQuery{
|
||||
IdsQuery: &session.IDsQuery{
|
||||
Ids: []string{"4", "5", "6"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
want: []query.SearchQuery{
|
||||
mustNewListQuery(t, query.SessionColumnID, []interface{}{"1", "2", "3"}, query.ListIn),
|
||||
mustNewListQuery(t, query.SessionColumnID, []interface{}{"4", "5", "6"}, query.ListIn),
|
||||
mustNewTextQuery(t, query.SessionColumnCreator, "789", query.TextEquals),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := sessionQueriesToQuery(tt.args.ctx, tt.args.queries)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sessionQueryToQuery(t *testing.T) {
|
||||
type args struct {
|
||||
query *session.SearchQuery
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want query.SearchQuery
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "invalid argument",
|
||||
args: args{&session.SearchQuery{
|
||||
Query: nil,
|
||||
}},
|
||||
wantErr: zerrors.ThrowInvalidArgument(nil, "GRPC-Sfefs", "List.Query.Invalid"),
|
||||
},
|
||||
{
|
||||
name: "ids query",
|
||||
args: args{&session.SearchQuery{
|
||||
Query: &session.SearchQuery_IdsQuery{
|
||||
IdsQuery: &session.IDsQuery{
|
||||
Ids: []string{"1", "2", "3"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
want: mustNewListQuery(t, query.SessionColumnID, []interface{}{"1", "2", "3"}, query.ListIn),
|
||||
},
|
||||
{
|
||||
name: "user id query",
|
||||
args: args{&session.SearchQuery{
|
||||
Query: &session.SearchQuery_UserIdQuery{
|
||||
UserIdQuery: &session.UserIDQuery{
|
||||
Id: "10",
|
||||
},
|
||||
},
|
||||
}},
|
||||
want: mustNewTextQuery(t, query.SessionColumnUserID, "10", query.TextEquals),
|
||||
},
|
||||
{
|
||||
name: "creation date query",
|
||||
args: args{&session.SearchQuery{
|
||||
Query: &session.SearchQuery_CreationDateQuery{
|
||||
CreationDateQuery: &session.CreationDateQuery{
|
||||
CreationDate: timestamppb.New(creationDate),
|
||||
Method: objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_LESS,
|
||||
},
|
||||
},
|
||||
}},
|
||||
want: mustNewTimestampQuery(t, query.SessionColumnCreationDate, creationDate, query.TimestampLess),
|
||||
},
|
||||
{
|
||||
name: "creation date query with default method",
|
||||
args: args{&session.SearchQuery{
|
||||
Query: &session.SearchQuery_CreationDateQuery{
|
||||
CreationDateQuery: &session.CreationDateQuery{
|
||||
CreationDate: timestamppb.New(creationDate),
|
||||
},
|
||||
},
|
||||
}},
|
||||
want: mustNewTimestampQuery(t, query.SessionColumnCreationDate, creationDate, query.TimestampEquals),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := sessionQueryToQuery(tt.args.query)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_userCheck(t *testing.T) {
|
||||
type args struct {
|
||||
user *session.CheckUser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want userSearch
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "nil user",
|
||||
args: args{nil},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "by user id",
|
||||
args: args{&session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: "foo",
|
||||
},
|
||||
}},
|
||||
want: userSearchByID{"foo"},
|
||||
},
|
||||
{
|
||||
name: "by user id",
|
||||
args: args{&session.CheckUser{
|
||||
Search: &session.CheckUser_LoginName{
|
||||
LoginName: "bar",
|
||||
},
|
||||
}},
|
||||
want: userSearchByLoginName{"bar"},
|
||||
},
|
||||
{
|
||||
name: "unimplemented error",
|
||||
args: args{&session.CheckUser{
|
||||
Search: nil,
|
||||
}},
|
||||
wantErr: zerrors.ThrowUnimplementedf(nil, "SESSION-d3b4g0", "user search %T not implemented", nil),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := userCheck(tt.args.user)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_userVerificationRequirementToDomain(t *testing.T) {
|
||||
type args struct {
|
||||
req session.UserVerificationRequirement
|
||||
}
|
||||
tests := []struct {
|
||||
args args
|
||||
want domain.UserVerificationRequirement
|
||||
}{
|
||||
{
|
||||
args: args{session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_UNSPECIFIED},
|
||||
want: domain.UserVerificationRequirementUnspecified,
|
||||
},
|
||||
{
|
||||
args: args{session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_REQUIRED},
|
||||
want: domain.UserVerificationRequirementRequired,
|
||||
},
|
||||
{
|
||||
args: args{session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_PREFERRED},
|
||||
want: domain.UserVerificationRequirementPreferred,
|
||||
},
|
||||
{
|
||||
args: args{session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_DISCOURAGED},
|
||||
want: domain.UserVerificationRequirementDiscouraged,
|
||||
},
|
||||
{
|
||||
args: args{999},
|
||||
want: domain.UserVerificationRequirementUnspecified,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.args.req.String(), func(t *testing.T) {
|
||||
got := userVerificationRequirementToDomain(tt.args.req)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_userAgentToCommand(t *testing.T) {
|
||||
type args struct {
|
||||
userAgent *session.UserAgent
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *domain.UserAgent
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
args: args{nil},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "all fields",
|
||||
args: args{&session.UserAgent{
|
||||
FingerprintId: gu.Ptr("fp1"),
|
||||
Ip: gu.Ptr("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: map[string]*session.UserAgent_HeaderValues{
|
||||
"hello": {
|
||||
Values: []string{"foo", "bar"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
want: &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{
|
||||
"hello": []string{"foo", "bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid ip",
|
||||
args: args{&session.UserAgent{
|
||||
FingerprintId: gu.Ptr("fp1"),
|
||||
Ip: gu.Ptr("oops"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: map[string]*session.UserAgent_HeaderValues{
|
||||
"hello": {
|
||||
Values: []string{"foo", "bar"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
want: &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: nil,
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{
|
||||
"hello": []string{"foo", "bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil fields",
|
||||
args: args{&session.UserAgent{}},
|
||||
want: &domain.UserAgent{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := userAgentToCommand(tt.args.userAgent)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user