fix: improvements for WebAuthN (#1105)

* add missing translations

* add missing passwordless funcs in api

* remove u2f with verification from setup in login
This commit is contained in:
Livio Amstutz 2020-12-15 16:44:16 +01:00 committed by GitHub
parent 7463bf4fe0
commit 71df1bcd0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 150 additions and 11 deletions

View File

@ -2,6 +2,7 @@ package auth
import ( import (
"context" "context"
"github.com/golang/protobuf/ptypes/empty" "github.com/golang/protobuf/ptypes/empty"
"github.com/caos/zitadel/pkg/grpc/auth" "github.com/caos/zitadel/pkg/grpc/auth"
@ -175,6 +176,14 @@ func (s *Server) RemoveMyMfaU2F(ctx context.Context, id *auth.WebAuthNTokenID) (
return &empty.Empty{}, err return &empty.Empty{}, err
} }
func (s *Server) GetMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNTokens, err error) {
tokens, err := s.repo.GetMyPasswordless(ctx)
if err != nil {
return nil, err
}
return webAuthNTokensFromModel(tokens), err
}
func (s *Server) AddMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) { func (s *Server) AddMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) {
u2f, err := s.repo.AddMyPasswordless(ctx) u2f, err := s.repo.AddMyPasswordless(ctx)
return verifyWebAuthNFromModel(u2f), err return verifyWebAuthNFromModel(u2f), err

View File

@ -436,3 +436,19 @@ func verifyWebAuthNFromModel(u2f *usr_model.WebAuthNToken) *auth.WebAuthNRespons
State: mfaStateFromModel(u2f.State), State: mfaStateFromModel(u2f.State),
} }
} }
func webAuthNTokensFromModel(tokens []*usr_model.WebAuthNToken) *auth.WebAuthNTokens {
result := make([]*auth.WebAuthNToken, len(tokens))
for i, token := range tokens {
result[i] = webAuthNTokenFromModel(token)
}
return &auth.WebAuthNTokens{Tokens: result}
}
func webAuthNTokenFromModel(token *usr_model.WebAuthNToken) *auth.WebAuthNToken {
return &auth.WebAuthNToken{
Id: token.WebAuthNTokenID,
Name: token.WebAuthNTokenName,
State: mfaStateFromModel(token.State),
}
}

View File

@ -2,9 +2,11 @@ package management
import ( import (
"context" "context"
"github.com/golang/protobuf/ptypes/empty"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/pkg/grpc/management" "github.com/caos/zitadel/pkg/grpc/management"
"github.com/golang/protobuf/ptypes/empty"
) )
func (s *Server) GetUserByID(ctx context.Context, id *management.UserID) (*management.UserView, error) { func (s *Server) GetUserByID(ctx context.Context, id *management.UserID) (*management.UserView, error) {
@ -231,6 +233,19 @@ func (s *Server) RemoveMfaU2F(ctx context.Context, webAuthNTokenID *management.W
return &empty.Empty{}, err return &empty.Empty{}, err
} }
func (s *Server) GetPasswordless(ctx context.Context, userID *management.UserID) (_ *management.WebAuthNTokens, err error) {
tokens, err := s.user.GetPasswordless(ctx, userID.Id)
if err != nil {
return nil, err
}
return webAuthNTokensFromModel(tokens), err
}
func (s *Server) RemovePasswordless(ctx context.Context, id *management.WebAuthNTokenID) (*empty.Empty, error) {
err := s.user.RemovePasswordless(ctx, id.UserId, id.Id)
return &empty.Empty{}, err
}
func (s *Server) SearchUserMemberships(ctx context.Context, in *management.UserMembershipSearchRequest) (*management.UserMembershipSearchResponse, error) { func (s *Server) SearchUserMemberships(ctx context.Context, in *management.UserMembershipSearchRequest) (*management.UserMembershipSearchResponse, error) {
request := userMembershipSearchRequestsToModel(in) request := userMembershipSearchRequestsToModel(in)
request.AppendUserIDQuery(in.UserId) request.AppendUserIDQuery(in.UserId)

View File

@ -629,3 +629,19 @@ func userChangesToMgtAPI(changes *usr_model.UserChanges) (_ []*management.Change
return result return result
} }
func webAuthNTokensFromModel(tokens []*usr_model.WebAuthNToken) *management.WebAuthNTokens {
result := make([]*management.WebAuthNToken, len(tokens))
for i, token := range tokens {
result[i] = webAuthNTokenFromModel(token)
}
return &management.WebAuthNTokens{Tokens: result}
}
func webAuthNTokenFromModel(token *usr_model.WebAuthNToken) *management.WebAuthNToken {
return &management.WebAuthNToken{
Id: token.WebAuthNTokenID,
Name: token.WebAuthNTokenName,
State: mfaStateFromModel(token.State),
}
}

View File

@ -326,10 +326,18 @@ func (repo *UserRepo) RemoveMyMFAU2F(ctx context.Context, webAuthNTokenID string
return repo.UserEvents.RemoveU2FToken(ctx, authz.GetCtxData(ctx).UserID, webAuthNTokenID) return repo.UserEvents.RemoveU2FToken(ctx, authz.GetCtxData(ctx).UserID, webAuthNTokenID)
} }
func (repo *UserRepo) GetPasswordless(ctx context.Context, userID string) ([]*model.WebAuthNToken, error) {
return repo.UserEvents.GetPasswordless(ctx, userID)
}
func (repo *UserRepo) AddPasswordless(ctx context.Context, userID string) (*model.WebAuthNToken, error) { func (repo *UserRepo) AddPasswordless(ctx context.Context, userID string) (*model.WebAuthNToken, error) {
return repo.UserEvents.AddPasswordless(ctx, userID, true) return repo.UserEvents.AddPasswordless(ctx, userID, true)
} }
func (repo *UserRepo) GetMyPasswordless(ctx context.Context) ([]*model.WebAuthNToken, error) {
return repo.UserEvents.GetPasswordless(ctx, authz.GetCtxData(ctx).UserID)
}
func (repo *UserRepo) AddMyPasswordless(ctx context.Context) (*model.WebAuthNToken, error) { func (repo *UserRepo) AddMyPasswordless(ctx context.Context) (*model.WebAuthNToken, error) {
return repo.UserEvents.AddPasswordless(ctx, authz.GetCtxData(ctx).UserID, false) return repo.UserEvents.AddPasswordless(ctx, authz.GetCtxData(ctx).UserID, false)
} }

View File

@ -32,6 +32,7 @@ type UserRepository interface {
VerifyMFAU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error VerifyMFAU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error
RemoveMFAU2F(ctx context.Context, userID, webAuthNTokenID string) error RemoveMFAU2F(ctx context.Context, userID, webAuthNTokenID string) error
GetPasswordless(ctx context.Context, id string) ([]*model.WebAuthNToken, error)
AddPasswordless(ctx context.Context, id string) (*model.WebAuthNToken, error) AddPasswordless(ctx context.Context, id string) (*model.WebAuthNToken, error)
VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error
RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error
@ -80,6 +81,7 @@ type myUserRepo interface {
VerifyMyMFAU2FSetup(ctx context.Context, tokenName string, data []byte) error VerifyMyMFAU2FSetup(ctx context.Context, tokenName string, data []byte) error
RemoveMyMFAU2F(ctx context.Context, webAuthNTokenID string) error RemoveMyMFAU2F(ctx context.Context, webAuthNTokenID string) error
GetMyPasswordless(ctx context.Context) ([]*model.WebAuthNToken, error)
AddMyPasswordless(ctx context.Context) (*model.WebAuthNToken, error) AddMyPasswordless(ctx context.Context) (*model.WebAuthNToken, error)
VerifyMyPasswordlessSetup(ctx context.Context, tokenName string, data []byte) error VerifyMyPasswordlessSetup(ctx context.Context, tokenName string, data []byte) error
RemoveMyPasswordless(ctx context.Context, webAuthNTokenID string) error RemoveMyPasswordless(ctx context.Context, webAuthNTokenID string) error

View File

@ -235,6 +235,14 @@ func (repo *UserRepo) RemoveU2F(ctx context.Context, userID, webAuthNTokenID str
return repo.UserEvents.RemoveU2FToken(ctx, userID, webAuthNTokenID) return repo.UserEvents.RemoveU2FToken(ctx, userID, webAuthNTokenID)
} }
func (repo *UserRepo) GetPasswordless(ctx context.Context, userID string) ([]*usr_model.WebAuthNToken, error) {
return repo.UserEvents.GetPasswordless(ctx, userID)
}
func (repo *UserRepo) RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error {
return repo.UserEvents.RemovePasswordlessToken(ctx, userID, webAuthNTokenID)
}
func (repo *UserRepo) SetOneTimePassword(ctx context.Context, password *usr_model.Password) (*usr_model.Password, error) { func (repo *UserRepo) SetOneTimePassword(ctx context.Context, password *usr_model.Password) (*usr_model.Password, error) {
policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID)
if err != nil && caos_errs.IsNotFound(err) { if err != nil && caos_errs.IsNotFound(err) {

View File

@ -34,6 +34,9 @@ type UserRepository interface {
RemoveOTP(ctx context.Context, userID string) error RemoveOTP(ctx context.Context, userID string) error
RemoveU2F(ctx context.Context, userID, webAuthNTokenID string) error RemoveU2F(ctx context.Context, userID, webAuthNTokenID string) error
GetPasswordless(ctx context.Context, userID string) ([]*model.WebAuthNToken, error)
RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error
SearchExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error) SearchExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error)
RemoveExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) error RemoveExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) error

View File

@ -446,9 +446,16 @@ EventTypes:
login: login:
added: Login Richtlinie hinzugefügt added: Login Richtlinie hinzugefügt
changed: Login Richtlinie geändert changed: Login Richtlinie geändert
removed: Login Richtline gelöscht
idpprovider: idpprovider:
added: Idp Provider zu Login Richtlinie hinzugefügt added: Idp Provider zu Login Richtlinie hinzugefügt
removed: Idp Provider aus Login Richtlinie gelöscht removed: Idp Provider aus Login Richtlinie gelöscht
secondfactor:
added: Zweitfaktor zu Login Richtlinie hinzugefügt
removed: Zweitfaktor aus Login Richtlinie gelöscht
multifactor:
added: Multifaktor zu Login Richtlinie hinzugefügt
removed: Multifaktor aus Login Richtlinie gelöscht
password: password:
complexity: complexity:
added: Passwort Komplexitäts Richtlinie hinzugefügt added: Passwort Komplexitäts Richtlinie hinzugefügt

View File

@ -446,9 +446,16 @@ EventTypes:
login: login:
added: Login Policy added added: Login Policy added
changed: Login Policy changed changed: Login Policy changed
removed: Login Policy removed
idpprovider: idpprovider:
added: Idp Provider added to Login Policy added: Idp Provider added to Login Policy
removed: Idp Provider removed from Login Policy removed: Idp Provider removed from Login Policy
secondfactor:
added: Second factor added to Login Policy
removed: Second factor removed from Login Policy
multifactor:
added: Multi factor added to Login Policy
removed: Multi factor removed from Login Policy
password: password:
complexity: complexity:
added: Password complexity policy added added: Password complexity policy added

View File

@ -136,16 +136,6 @@ func (u *UserView) MFATypesSetupPossible(level req_model.MFALevel, policy *iam_m
} }
} }
//PLANNED: add sms //PLANNED: add sms
fallthrough
case req_model.MFALevelMultiFactor:
if policy.HasMultiFactors() {
for _, mfaType := range policy.MultiFactors {
switch mfaType {
case iam_model.MultiFactorTypeU2FWithPIN:
types = append(types, req_model.MFATypeU2FUserVerification)
}
}
}
} }
return types return types
} }

View File

@ -1410,6 +1410,14 @@ func (es *UserEventstore) VerifyMFAU2F(ctx context.Context, userID string, crede
return finishErr return finishErr
} }
func (es *UserEventstore) GetPasswordless(ctx context.Context, userID string) ([]*usr_model.WebAuthNToken, error) {
user, err := es.HumanByID(ctx, userID)
if err != nil {
return nil, err
}
return user.PasswordlessTokens, nil
}
func (es *UserEventstore) AddPasswordless(ctx context.Context, userID string, isLoginUI bool) (*usr_model.WebAuthNToken, error) { func (es *UserEventstore) AddPasswordless(ctx context.Context, userID string, isLoginUI bool) (*usr_model.WebAuthNToken, error) {
user, err := es.HumanByID(ctx, userID) user, err := es.HumanByID(ctx, userID)
if err != nil { if err != nil {

View File

@ -337,6 +337,15 @@ service AuthService {
}; };
} }
rpc GetMyPasswordless(google.protobuf.Empty) returns (WebAuthNTokens) {
option (google.api.http) = {
get: "/users/me/passwordless"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "authenticated"
};
}
rpc AddMyPasswordless(google.protobuf.Empty) returns (WebAuthNResponse) { rpc AddMyPasswordless(google.protobuf.Empty) returns (WebAuthNResponse) {
option (google.api.http) = { option (google.api.http) = {
post: "/users/me/passwordless" post: "/users/me/passwordless"
@ -666,6 +675,16 @@ message MfaOtpResponse {
MFAState state = 4; MFAState state = 4;
} }
message WebAuthNTokens {
repeated WebAuthNToken tokens = 1;
}
message WebAuthNToken {
string id = 1;
string name = 2;
MFAState state = 3;
}
message WebAuthNResponse { message WebAuthNResponse {
string id = 1; string id = 1;
bytes public_key = 2; bytes public_key = 2;

View File

@ -409,6 +409,26 @@ service ManagementService {
}; };
} }
rpc GetPasswordless(UserID) returns (WebAuthNTokens) {
option (google.api.http) = {
get: "/users/{id}/passwordless"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "user.read"
};
}
rpc RemovePasswordless(WebAuthNTokenID) returns (google.protobuf.Empty) {
option (google.api.http) = {
delete: "/users/{user_id}/passwordless"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "user.write"
};
}
// Sends an Notification (Email/SMS) with a password reset Link // Sends an Notification (Email/SMS) with a password reset Link
rpc SendSetPasswordNotification(SetPasswordNotificationRequest) returns (google.protobuf.Empty) { rpc SendSetPasswordNotification(SetPasswordNotificationRequest) returns (google.protobuf.Empty) {
option (google.api.http) = { option (google.api.http) = {
@ -1656,6 +1676,16 @@ message UserID {
string id = 1 [(validate.rules).string.min_len = 1]; string id = 1 [(validate.rules).string.min_len = 1];
} }
message WebAuthNTokens {
repeated WebAuthNToken tokens = 1;
}
message WebAuthNToken {
string id = 1;
string name = 2;
MFAState state = 3;
}
message WebAuthNTokenID { message WebAuthNTokenID {
string user_id = 1 [(validate.rules).string.min_len = 1]; string user_id = 1 [(validate.rules).string.min_len = 1];
string id = 2 [(validate.rules).string.min_len = 1]; string id = 2 [(validate.rules).string.min_len = 1];
@ -3097,6 +3127,7 @@ enum PasswordlessType {
PASSWORDLESSTYPE_ALLOWED = 1; PASSWORDLESSTYPE_ALLOWED = 1;
} }
message IdpProviderID { message IdpProviderID {
string idp_config_id = 1 [(validate.rules).string = {min_len: 1}]; string idp_config_id = 1 [(validate.rules).string = {min_len: 1}];
} }