feat: v2alpha user service idp endpoints (#5879)

* feat: v2alpha user service idp endpoints

* feat: v2alpha user service intent endpoints

* begin idp intents (callback)

* some cleanup

* runnable idp authentication

* cleanup

* proto cleanup

* retrieve idp info

* improve success and failure handling

* some unit tests

* grpc unit tests

* add permission check AddUserIDPLink

* feat: v2alpha intent writemodel refactoring

* feat: v2alpha intent writemodel refactoring

* feat: v2alpha intent writemodel refactoring

* provider from write model

* fix idp type model and add integration tests

* proto cleanup

* fix integration test

* add missing import

* add more integration tests

* auth url test

* feat: v2alpha intent writemodel refactoring

* remove unused functions

* check token on RetrieveIdentityProviderInformation

* feat: v2alpha intent writemodel refactoring

* fix TestServer_RetrieveIdentityProviderInformation

* fix test

* i18n and linting

* feat: v2alpha intent review changes

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>
This commit is contained in:
Stefan Benz
2023-05-24 20:29:58 +02:00
committed by GitHub
parent 767b3d7e65
commit fa8f191812
35 changed files with 3560 additions and 19 deletions

View File

@@ -2,15 +2,19 @@ package user
import (
"context"
"encoding/base64"
"io"
"golang.org/x/text/language"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2alpha"
user "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha"
)
@@ -56,6 +60,14 @@ func addUserRequestToAddHuman(req *user.AddHumanUserRequest) (*command.AddHuman,
Value: metadataEntry.GetValue(),
}
}
links := make([]*command.AddLink, len(req.GetIdpLinks()))
for i, link := range req.GetIdpLinks() {
links[i] = &command.AddLink{
IDPID: link.GetIdpId(),
IDPExternalID: link.GetIdpExternalId(),
DisplayName: link.GetDisplayName(),
}
}
return &command.AddHuman{
ID: req.GetUserId(),
Username: username,
@@ -76,9 +88,9 @@ func addUserRequestToAddHuman(req *user.AddHumanUserRequest) (*command.AddHuman,
BcryptedPassword: bcryptedPassword,
PasswordChangeRequired: passwordChangeRequired,
Passwordless: false,
ExternalIDP: false,
Register: false,
Metadata: metadata,
Links: links,
}, nil
}
@@ -107,3 +119,95 @@ func hashedPasswordToCommand(hashed *user.HashedPassword) (string, error) {
}
return hashed.GetHash(), nil
}
func (s *Server) AddIDPLink(ctx context.Context, req *user.AddIDPLinkRequest) (_ *user.AddIDPLinkResponse, err error) {
orgID := authz.GetCtxData(ctx).OrgID
details, err := s.command.AddUserIDPLink(ctx, req.UserId, orgID, &domain.UserIDPLink{
IDPConfigID: req.GetIdpLink().GetIdpId(),
ExternalUserID: req.GetIdpLink().GetIdpExternalId(),
DisplayName: req.GetIdpLink().GetDisplayName(),
})
if err != nil {
return nil, err
}
return &user.AddIDPLinkResponse{
Details: object.DomainToDetailsPb(details),
}, nil
}
func (s *Server) StartIdentityProviderFlow(ctx context.Context, req *user.StartIdentityProviderFlowRequest) (_ *user.StartIdentityProviderFlowResponse, err error) {
id, details, err := s.command.CreateIntent(ctx, req.GetIdpId(), req.GetSuccessUrl(), req.GetFailureUrl(), authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
authURL, err := s.command.AuthURLFromProvider(ctx, req.GetIdpId(), id, s.idpCallback(ctx))
if err != nil {
return nil, err
}
return &user.StartIdentityProviderFlowResponse{
Details: object.DomainToDetailsPb(details),
NextStep: &user.StartIdentityProviderFlowResponse_AuthUrl{AuthUrl: authURL},
}, nil
}
func (s *Server) RetrieveIdentityProviderInformation(ctx context.Context, req *user.RetrieveIdentityProviderInformationRequest) (_ *user.RetrieveIdentityProviderInformationResponse, err error) {
intent, err := s.command.GetIntentWriteModel(ctx, req.GetIntentId(), authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
if err := s.checkIntentToken(req.GetToken(), intent.AggregateID); err != nil {
return nil, err
}
if intent.State != domain.IDPIntentStateSucceeded {
return nil, errors.ThrowPreconditionFailed(nil, "IDP-Hk38e", "Errors.Intent.NotSucceeded")
}
return intentToIDPInformationPb(intent, s.idpAlg)
}
func intentToIDPInformationPb(intent *command.IDPIntentWriteModel, alg crypto.EncryptionAlgorithm) (_ *user.RetrieveIdentityProviderInformationResponse, err error) {
var idToken *string
if intent.IDPIDToken != "" {
idToken = &intent.IDPIDToken
}
var accessToken string
if intent.IDPAccessToken != nil {
accessToken, err = crypto.DecryptString(intent.IDPAccessToken, alg)
if err != nil {
return nil, err
}
}
return &user.RetrieveIdentityProviderInformationResponse{
Details: &object_pb.Details{
Sequence: intent.ProcessedSequence,
ChangeDate: timestamppb.New(intent.ChangeDate),
ResourceOwner: intent.ResourceOwner,
},
IdpInformation: &user.IDPInformation{
Access: &user.IDPInformation_Oauth{
Oauth: &user.IDPOAuthAccessInformation{
AccessToken: accessToken,
IdToken: idToken,
},
},
IdpInformation: intent.IDPUser,
},
}, nil
}
func (s *Server) checkIntentToken(token string, intentID string) error {
if token == "" {
return errors.ThrowPermissionDenied(nil, "IDP-Sfefs", "Errors.Intent.InvalidToken")
}
data, err := base64.RawURLEncoding.DecodeString(token)
if err != nil {
return errors.ThrowPermissionDenied(err, "IDP-Swg31", "Errors.Intent.InvalidToken")
}
decryptedToken, err := s.idpAlg.Decrypt(data, s.idpAlg.EncryptionKeyID())
if err != nil {
return errors.ThrowPermissionDenied(err, "IDP-Sf4gt", "Errors.Intent.InvalidToken")
}
if string(decryptedToken) != intentID {
return errors.ThrowPermissionDenied(nil, "IDP-dkje3", "Errors.Intent.InvalidToken")
}
return nil
}