From 7295383621ea4880b1b5a033a2034c7d2b57810b Mon Sep 17 00:00:00 2001 From: Fabi <38692350+fgerschwiler@users.noreply.github.com> Date: Fri, 28 Aug 2020 09:44:43 +0200 Subject: [PATCH] fix: translation (#647) * fix: translation * fix: translation * fix: translation * fix: remove unused code * fix: log err --- internal/api/authz/context.go | 4 +- internal/api/authz/permissions_test.go | 4 +- internal/api/authz/token.go | 14 ++-- internal/api/authz/token_test.go | 2 +- internal/api/grpc/{ => errors}/caos_errors.go | 11 ++- .../api/grpc/{ => errors}/caos_errors_test.go | 12 ++-- .../middleware/auth_interceptor_test.go | 4 +- .../server/middleware/error_interceptor.go | 14 ++-- .../middleware/error_interceptor_test.go | 2 +- .../middleware/translation_interceptor.go | 3 + .../api/grpc/server/middleware/translator.go | 13 ++++ internal/api/grpc/server/server.go | 7 +- internal/api/oidc/client.go | 4 ++ .../eventsourcing/eventstore/token.go | 7 +- .../repository/eventsourcing/handler/token.go | 12 ++++ .../repository/eventsourcing/view/token.go | 31 ++++++--- .../eventstore/token_verifier.go | 12 ++-- internal/errors/caos_error.go | 4 ++ internal/errors/error.go | 1 + internal/i18n/i18n.go | 5 ++ internal/token/model/token.go | 23 ++++--- internal/token/repository/view/model/token.go | 69 ++++++++++--------- internal/token/repository/view/token.go | 24 +++++++ internal/tracing/span.go | 4 +- migrations/cockroach/V1.8__token.sql | 2 + 25 files changed, 184 insertions(+), 104 deletions(-) rename internal/api/grpc/{ => errors}/caos_errors.go (88%) rename internal/api/grpc/{ => errors}/caos_errors_test.go (90%) create mode 100644 migrations/cockroach/V1.8__token.sql diff --git a/internal/api/authz/context.go b/internal/api/authz/context.go index 7d1362940b..515b086323 100644 --- a/internal/api/authz/context.go +++ b/internal/api/authz/context.go @@ -43,7 +43,7 @@ func VerifyTokenAndWriteCtxData(ctx context.Context, token, orgID string, t *Tok } } - userID, clientID, agentID, err := verifyAccessToken(ctx, token, t, method) + userID, clientID, agentID, prefLang, err := verifyAccessToken(ctx, token, t, method) if err != nil { return nil, err } @@ -54,7 +54,7 @@ func VerifyTokenAndWriteCtxData(ctx context.Context, token, orgID string, t *Tok if err := checkOrigin(ctx, origins); err != nil { return nil, err } - return context.WithValue(ctx, dataKey, CtxData{UserID: userID, OrgID: orgID, ProjectID: projectID, AgentID: agentID}), nil + return context.WithValue(ctx, dataKey, CtxData{UserID: userID, OrgID: orgID, ProjectID: projectID, AgentID: agentID, PreferredLanguage: prefLang}), nil } func SetCtxData(ctx context.Context, ctxData CtxData) context.Context { diff --git a/internal/api/authz/permissions_test.go b/internal/api/authz/permissions_test.go index 32347fd236..d40fb21d29 100644 --- a/internal/api/authz/permissions_test.go +++ b/internal/api/authz/permissions_test.go @@ -15,8 +15,8 @@ type testVerifier struct { grant *Grant } -func (v *testVerifier) VerifyAccessToken(ctx context.Context, token, clientID string) (string, string, error) { - return "userID", "agentID", nil +func (v *testVerifier) VerifyAccessToken(ctx context.Context, token, clientID string) (string, string, string, error) { + return "userID", "agentID", "de", nil } func (v *testVerifier) ResolveGrants(ctx context.Context) (*Grant, error) { diff --git a/internal/api/authz/token.go b/internal/api/authz/token.go index fca6111490..277ff7379b 100644 --- a/internal/api/authz/token.go +++ b/internal/api/authz/token.go @@ -19,7 +19,7 @@ type TokenVerifier struct { } type authZRepo interface { - VerifyAccessToken(ctx context.Context, token, clientID string) (userID, agentID string, err error) + VerifyAccessToken(ctx context.Context, token, clientID string) (userID, agentID, prefLang string, err error) VerifierClientID(ctx context.Context, name string) (clientID string, err error) ResolveGrants(ctx context.Context) (grant *Grant, err error) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (projectID string, origins []string, err error) @@ -30,13 +30,13 @@ func Start(authZRepo authZRepo) (v *TokenVerifier) { return &TokenVerifier{authZRepo: authZRepo} } -func (v *TokenVerifier) VerifyAccessToken(ctx context.Context, token string, method string) (userID, clientID, agentID string, err error) { +func (v *TokenVerifier) VerifyAccessToken(ctx context.Context, token string, method string) (userID, clientID, agentID, prefLang string, err error) { clientID, err = v.clientIDFromMethod(ctx, method) if err != nil { - return "", "", "", err + return "", "", "", "", err } - userID, agentID, err = v.authZRepo.VerifyAccessToken(ctx, token, clientID) - return userID, clientID, agentID, err + userID, agentID, prefLang, err = v.authZRepo.VerifyAccessToken(ctx, token, clientID) + return userID, clientID, agentID, prefLang, err } type client struct { @@ -101,10 +101,10 @@ func (v *TokenVerifier) CheckAuthMethod(method string) (Option, bool) { return authOpt, ok } -func verifyAccessToken(ctx context.Context, token string, t *TokenVerifier, method string) (userID, clientID, agentID string, err error) { +func verifyAccessToken(ctx context.Context, token string, t *TokenVerifier, method string) (userID, clientID, agentID, prefLang string, err error) { parts := strings.Split(token, BearerPrefix) if len(parts) != 2 { - return "", "", "", caos_errs.ThrowUnauthenticated(nil, "AUTH-7fs1e", "invalid auth header") + return "", "", "", "", caos_errs.ThrowUnauthenticated(nil, "AUTH-7fs1e", "invalid auth header") } return t.VerifyAccessToken(ctx, parts[1], method) } diff --git a/internal/api/authz/token_test.go b/internal/api/authz/token_test.go index f4980a501c..2303e4ac69 100644 --- a/internal/api/authz/token_test.go +++ b/internal/api/authz/token_test.go @@ -58,7 +58,7 @@ func Test_VerifyAccessToken(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, _, _, err := verifyAccessToken(tt.args.ctx, tt.args.token, tt.args.verifier, tt.args.method) + _, _, _, _, err := verifyAccessToken(tt.args.ctx, tt.args.token, tt.args.verifier, tt.args.method) if tt.wantErr && err == nil { t.Errorf("got wrong result, should get err: actual: %v ", err) } diff --git a/internal/api/grpc/caos_errors.go b/internal/api/grpc/errors/caos_errors.go similarity index 88% rename from internal/api/grpc/caos_errors.go rename to internal/api/grpc/errors/caos_errors.go index f2c2ad0f81..10f4fd3cff 100644 --- a/internal/api/grpc/caos_errors.go +++ b/internal/api/grpc/errors/caos_errors.go @@ -1,16 +1,15 @@ -package grpc +package errors import ( "context" "github.com/caos/logging" caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/i18n" "github.com/caos/zitadel/pkg/grpc/message" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -func CaosToGRPCError(ctx context.Context, err error, translator *i18n.Translator) error { +func CaosToGRPCError(ctx context.Context, err error) error { if err == nil { return nil } @@ -19,10 +18,8 @@ func CaosToGRPCError(ctx context.Context, err error, translator *i18n.Translator return status.Convert(err).Err() } msg := key - if translator != nil { - msg = translator.LocalizeFromCtx(ctx, key, nil) - msg += " (" + id + ")" - } + msg += " (" + id + ")" + s, err := status.New(code, msg).WithDetails(&message.ErrorDetail{Id: id, Message: key}) if err != nil { logging.Log("GRPC-gIeRw").WithError(err).Debug("unable to add detail") diff --git a/internal/api/grpc/caos_errors_test.go b/internal/api/grpc/errors/caos_errors_test.go similarity index 90% rename from internal/api/grpc/caos_errors_test.go rename to internal/api/grpc/errors/caos_errors_test.go index a4614d32d5..c8d935c2d6 100644 --- a/internal/api/grpc/caos_errors_test.go +++ b/internal/api/grpc/errors/caos_errors_test.go @@ -1,4 +1,4 @@ -package grpc +package errors import ( "context" @@ -8,13 +8,11 @@ import ( "google.golang.org/grpc/codes" caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/i18n" ) func TestCaosToGRPCError(t *testing.T) { type args struct { - err error - translator *i18n.Translator + err error } tests := []struct { name string @@ -28,18 +26,18 @@ func TestCaosToGRPCError(t *testing.T) { }, { "unknown error", - args{errors.New("unknown"), nil}, + args{errors.New("unknown")}, true, }, { "caos error", - args{caos_errs.ThrowInternal(nil, "", "message"), nil}, + args{caos_errs.ThrowInternal(nil, "", "message")}, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := CaosToGRPCError(context.Background(), tt.args.err, tt.args.translator); (err != nil) != tt.wantErr { + if err := CaosToGRPCError(context.Background(), tt.args.err); (err != nil) != tt.wantErr { t.Errorf("CaosToGRPCError() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/internal/api/grpc/server/middleware/auth_interceptor_test.go b/internal/api/grpc/server/middleware/auth_interceptor_test.go index fb095a71a7..570a4fbd94 100644 --- a/internal/api/grpc/server/middleware/auth_interceptor_test.go +++ b/internal/api/grpc/server/middleware/auth_interceptor_test.go @@ -21,8 +21,8 @@ var ( type verifierMock struct{} -func (v *verifierMock) VerifyAccessToken(ctx context.Context, token, clientID string) (string, string, error) { - return "", "", nil +func (v *verifierMock) VerifyAccessToken(ctx context.Context, token, clientID string) (string, string, string, error) { + return "", "", "", nil } func (v *verifierMock) ResolveGrants(ctx context.Context) (*authz.Grant, error) { return nil, nil diff --git a/internal/api/grpc/server/middleware/error_interceptor.go b/internal/api/grpc/server/middleware/error_interceptor.go index c2d929ebc3..31a4f1d27b 100644 --- a/internal/api/grpc/server/middleware/error_interceptor.go +++ b/internal/api/grpc/server/middleware/error_interceptor.go @@ -2,24 +2,20 @@ package middleware import ( "context" + "github.com/caos/zitadel/internal/api/grpc/errors" - "golang.org/x/text/language" "google.golang.org/grpc" - grpc_util "github.com/caos/zitadel/internal/api/grpc" - "github.com/caos/zitadel/internal/i18n" _ "github.com/caos/zitadel/internal/statik" ) -func ErrorHandler(defaultLanguage language.Tag) grpc.UnaryServerInterceptor { - translator := newZitadelTranslator(defaultLanguage) - +func ErrorHandler() grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - return toGRPCError(ctx, req, handler, translator) + return toGRPCError(ctx, req, handler) } } -func toGRPCError(ctx context.Context, req interface{}, handler grpc.UnaryHandler, translator *i18n.Translator) (interface{}, error) { +func toGRPCError(ctx context.Context, req interface{}, handler grpc.UnaryHandler) (interface{}, error) { resp, err := handler(ctx, req) - return resp, grpc_util.CaosToGRPCError(ctx, err, translator) + return resp, errors.CaosToGRPCError(ctx, err) } diff --git a/internal/api/grpc/server/middleware/error_interceptor_test.go b/internal/api/grpc/server/middleware/error_interceptor_test.go index 1bb868af65..82dc35f64a 100644 --- a/internal/api/grpc/server/middleware/error_interceptor_test.go +++ b/internal/api/grpc/server/middleware/error_interceptor_test.go @@ -50,7 +50,7 @@ func Test_toGRPCError(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := toGRPCError(tt.args.ctx, tt.args.req, tt.args.handler, nil) + got, err := toGRPCError(tt.args.ctx, tt.args.req, tt.args.handler) if (err != nil) != tt.res.wantErr { t.Errorf("toGRPCError() error = %v, wantErr %v", err, tt.res.wantErr) return diff --git a/internal/api/grpc/server/middleware/translation_interceptor.go b/internal/api/grpc/server/middleware/translation_interceptor.go index c1fd704a0a..ed760c53f7 100644 --- a/internal/api/grpc/server/middleware/translation_interceptor.go +++ b/internal/api/grpc/server/middleware/translation_interceptor.go @@ -18,6 +18,9 @@ func TranslationHandler(defaultLanguage language.Tag) func(ctx context.Context, if loc, ok := resp.(localizers); ok && resp != nil { translateFields(ctx, loc, translator) } + if err != nil { + err = translateError(ctx, err, translator) + } return resp, err } } diff --git a/internal/api/grpc/server/middleware/translator.go b/internal/api/grpc/server/middleware/translator.go index 5a72415ed7..970e2b89d2 100644 --- a/internal/api/grpc/server/middleware/translator.go +++ b/internal/api/grpc/server/middleware/translator.go @@ -2,6 +2,8 @@ package middleware import ( "context" + "errors" + caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/logging" "github.com/caos/zitadel/internal/i18n" @@ -26,6 +28,17 @@ func translateFields(ctx context.Context, object localizers, translator *i18n.Tr } } +func translateError(ctx context.Context, err error, translator *i18n.Translator) error { + if translator == nil || err == nil { + return err + } + caosErr := new(caos_errs.CaosError) + if errors.As(err, &caosErr) { + caosErr.SetMessage(translator.LocalizeFromCtx(ctx, caosErr.GetMessage(), nil)) + } + return caosErr +} + func newZitadelTranslator(defaultLanguage language.Tag) *i18n.Translator { return translatorFromNamespace("zitadel", defaultLanguage) } diff --git a/internal/api/grpc/server/server.go b/internal/api/grpc/server/server.go index dda265d601..247a0dd58d 100644 --- a/internal/api/grpc/server/server.go +++ b/internal/api/grpc/server/server.go @@ -30,14 +30,13 @@ func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, lang l middleware.TracingStatsServer(http.Healthz, http.Readiness, http.Validation), grpc.UnaryInterceptor( grpc_middleware.ChainUnaryServer( - middleware.ErrorHandler(lang), + middleware.ErrorHandler(), + middleware.AuthorizationInterceptor(verifier, authConfig), middleware.TranslationHandler(lang), - grpc_middleware.ChainUnaryServer( - middleware.AuthorizationInterceptor(verifier, authConfig), - ), ), ), ) + } func Serve(ctx context.Context, server *grpc.Server, port string) { diff --git a/internal/api/oidc/client.go b/internal/api/oidc/client.go index a18b495d3c..586a26ebf6 100644 --- a/internal/api/oidc/client.go +++ b/internal/api/oidc/client.go @@ -2,6 +2,8 @@ package oidc import ( "context" + "github.com/caos/logging" + "golang.org/x/text/language" "github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/op" @@ -78,6 +80,8 @@ func (o *OPStorage) GetUserinfoFromScopes(ctx context.Context, userID string, sc userInfo.PreferredUsername = user.PreferredLoginName userInfo.UpdatedAt = user.ChangeDate userInfo.Gender = oidc.Gender(getGender(user.Gender)) + userInfo.Locale, err = language.Parse(user.PreferredLanguage) + logging.Log("OIDC-4ks9F").OnError(err).Debug("unable to parse locale") case scopePhone: userInfo.PhoneNumber = user.Phone userInfo.PhoneNumberVerified = user.IsPhoneVerified diff --git a/internal/auth/repository/eventsourcing/eventstore/token.go b/internal/auth/repository/eventsourcing/eventstore/token.go index 0bc4339b93..98240e9023 100644 --- a/internal/auth/repository/eventsourcing/eventstore/token.go +++ b/internal/auth/repository/eventsourcing/eventstore/token.go @@ -14,7 +14,12 @@ type TokenRepo struct { } func (repo *TokenRepo) CreateToken(ctx context.Context, agentID, applicationID, userID string, audience, scopes []string, lifetime time.Duration) (*token_model.Token, error) { - token, err := repo.View.CreateToken(agentID, applicationID, userID, audience, scopes, lifetime) + preferredLanguage := "" + user, _ := repo.View.UserByID(userID) + if user != nil { + preferredLanguage = user.PreferredLanguage + } + token, err := repo.View.CreateToken(agentID, applicationID, userID, preferredLanguage, audience, scopes, lifetime) if err != nil { return nil, err } diff --git a/internal/auth/repository/eventsourcing/handler/token.go b/internal/auth/repository/eventsourcing/handler/token.go index ce07810fd8..2d23c53e50 100644 --- a/internal/auth/repository/eventsourcing/handler/token.go +++ b/internal/auth/repository/eventsourcing/handler/token.go @@ -3,6 +3,7 @@ package handler import ( "context" "encoding/json" + view_model "github.com/caos/zitadel/internal/user/repository/view/model" "github.com/caos/logging" @@ -43,6 +44,17 @@ func (u *Token) EventQuery() (*models.SearchQuery, error) { func (u *Token) Reduce(event *models.Event) (err error) { switch event.Type { + case user_es_model.UserProfileChanged: + user := new(view_model.UserView) + user.AppendEvent(event) + tokens, err := u.view.TokensByUserID(event.AggregateID) + if err != nil { + return err + } + for _, token := range tokens { + token.PreferredLanguage = user.PreferredLanguage + } + return u.view.PutTokens(tokens, event.Sequence) case user_es_model.SignedOut: id, err := agentIDFromSession(event) if err != nil { diff --git a/internal/auth/repository/eventsourcing/view/token.go b/internal/auth/repository/eventsourcing/view/token.go index c76e9e8952..3737e60356 100644 --- a/internal/auth/repository/eventsourcing/view/token.go +++ b/internal/auth/repository/eventsourcing/view/token.go @@ -16,25 +16,30 @@ func (v *View) TokenByID(tokenID string) (*model.Token, error) { return view.TokenByID(v.Db, tokenTable, tokenID) } +func (v *View) TokensByUserID(userID string) ([]*model.Token, error) { + return view.TokensByUserID(v.Db, tokenTable, userID) +} + func (v *View) IsTokenValid(tokenID string) (bool, error) { return view.IsTokenValid(v.Db, tokenTable, tokenID) } -func (v *View) CreateToken(agentID, applicationID, userID string, audience, scopes []string, lifetime time.Duration) (*model.Token, error) { +func (v *View) CreateToken(agentID, applicationID, userID, preferredLanguage string, audience, scopes []string, lifetime time.Duration) (*model.Token, error) { id, err := v.idGenerator.Next() if err != nil { return nil, err } now := time.Now().UTC() token := &model.Token{ - ID: id, - CreationDate: now, - UserID: userID, - ApplicationID: applicationID, - UserAgentID: agentID, - Scopes: scopes, - Audience: audience, - Expiration: now.Add(lifetime), + ID: id, + CreationDate: now, + UserID: userID, + ApplicationID: applicationID, + UserAgentID: agentID, + Scopes: scopes, + Audience: audience, + Expiration: now.Add(lifetime), + PreferredLanguage: preferredLanguage, } if err := view.PutToken(v.Db, tokenTable, token); err != nil { return nil, err @@ -50,6 +55,14 @@ func (v *View) PutToken(token *model.Token) error { return v.ProcessedTokenSequence(token.Sequence) } +func (v *View) PutTokens(token []*model.Token, sequence uint64) error { + err := view.PutTokens(v.Db, tokenTable, token...) + if err != nil { + return err + } + return v.ProcessedTokenSequence(sequence) +} + func (v *View) DeleteToken(tokenID string, eventSequence uint64) error { err := view.DeleteToken(v.Db, tokenTable, tokenID) if err != nil { diff --git a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go index 7cfed3ab70..c4b041c8ce 100644 --- a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go +++ b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go @@ -19,26 +19,26 @@ type TokenVerifierRepo struct { View *view.View } -func (repo *TokenVerifierRepo) VerifyAccessToken(ctx context.Context, tokenString, clientID string) (userID string, agentID string, err error) { +func (repo *TokenVerifierRepo) VerifyAccessToken(ctx context.Context, tokenString, clientID string) (userID string, agentID string, prefLang string, err error) { //TODO: use real key tokenID, err := crypto.DecryptAESString(tokenString, string(repo.TokenVerificationKey[:32])) if err != nil { - return "", "", caos_errs.ThrowUnauthenticated(nil, "APP-8EF0zZ", "invalid token") + return "", "", "", caos_errs.ThrowUnauthenticated(nil, "APP-8EF0zZ", "invalid token") } token, err := repo.View.TokenByID(tokenID) if err != nil { - return "", "", caos_errs.ThrowUnauthenticated(err, "APP-BxUSiL", "invalid token") + return "", "", "", caos_errs.ThrowUnauthenticated(err, "APP-BxUSiL", "invalid token") } if !token.Expiration.After(time.Now().UTC()) { - return "", "", caos_errs.ThrowUnauthenticated(err, "APP-k9KS0", "invalid token") + return "", "", "", caos_errs.ThrowUnauthenticated(err, "APP-k9KS0", "invalid token") } for _, aud := range token.Audience { if clientID == aud { - return token.UserID, token.UserAgentID, nil + return token.UserID, token.UserAgentID, token.PreferredLanguage, nil } } - return "", "", caos_errs.ThrowUnauthenticated(nil, "APP-Zxfako", "invalid audience") + return "", "", "", caos_errs.ThrowUnauthenticated(nil, "APP-Zxfako", "invalid audience") } func (repo *TokenVerifierRepo) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (projectID string, origins []string, err error) { diff --git a/internal/errors/caos_error.go b/internal/errors/caos_error.go index 5559e5728f..b4a04d615e 100644 --- a/internal/errors/caos_error.go +++ b/internal/errors/caos_error.go @@ -44,6 +44,10 @@ func (err *CaosError) GetMessage() string { return err.Message } +func (err *CaosError) SetMessage(msg string) { + err.Message = msg +} + func (err *CaosError) GetID() string { return err.ID } diff --git a/internal/errors/error.go b/internal/errors/error.go index 7e25b0ed39..31d1fc6843 100644 --- a/internal/errors/error.go +++ b/internal/errors/error.go @@ -9,6 +9,7 @@ import ( type Error interface { GetParent() error GetMessage() string + SetMessage(string) GetID() string } diff --git a/internal/i18n/i18n.go b/internal/i18n/i18n.go index 4229abd6ee..b0cb458e6f 100644 --- a/internal/i18n/i18n.go +++ b/internal/i18n/i18n.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "github.com/BurntSushi/toml" + "github.com/caos/zitadel/internal/api/authz" "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" "io/ioutil" "net/http" @@ -130,6 +131,10 @@ func (t *Translator) langsFromRequest(r *http.Request) []string { func (t *Translator) langsFromCtx(ctx context.Context) []string { langs := make([]string, 0) if ctx != nil { + ctxData := authz.GetCtxData(ctx) + if ctxData.PreferredLanguage != "" { + langs = append(langs, authz.GetCtxData(ctx).PreferredLanguage) + } langs = append(langs, getAcceptLanguageHeader(ctx)) } return langs diff --git a/internal/token/model/token.go b/internal/token/model/token.go index f0ab1bd1c4..5f9a8d0843 100644 --- a/internal/token/model/token.go +++ b/internal/token/model/token.go @@ -7,17 +7,18 @@ import ( ) type Token struct { - ID string - CreationDate time.Time - ChangeDate time.Time - ResourceOwner string - UserID string - ApplicationID string - UserAgentID string - Audience []string - Expiration time.Time - Scopes []string - Sequence uint64 + ID string + CreationDate time.Time + ChangeDate time.Time + ResourceOwner string + UserID string + ApplicationID string + UserAgentID string + Audience []string + Expiration time.Time + Scopes []string + Sequence uint64 + PreferredLanguage string } type TokenSearchRequest struct { diff --git a/internal/token/repository/view/model/token.go b/internal/token/repository/view/model/token.go index 1b08051a96..bd0cb83168 100644 --- a/internal/token/repository/view/model/token.go +++ b/internal/token/repository/view/model/token.go @@ -18,47 +18,50 @@ const ( ) type Token struct { - ID string `json:"-" gorm:"column:id;primary_key"` - CreationDate time.Time `json:"-" gorm:"column:creation_date"` - ChangeDate time.Time `json:"-" gorm:"column:change_date"` - ResourceOwner string `json:"-" gorm:"column:resource_owner"` - UserID string `json:"-" gorm:"column:user_id"` - ApplicationID string `json:"-" gorm:"column:application_id"` - UserAgentID string `json:"-" gorm:"column:user_agent_id"` - Audience pq.StringArray `json:"-" gorm:"column:audience"` - Scopes pq.StringArray `json:"-" gorm:"column:scopes"` - Expiration time.Time `json:"-" gorm:"column:expiration"` - Sequence uint64 `json:"-" gorm:"column:sequence"` + ID string `json:"-" gorm:"column:id;primary_key"` + CreationDate time.Time `json:"-" gorm:"column:creation_date"` + ChangeDate time.Time `json:"-" gorm:"column:change_date"` + ResourceOwner string `json:"-" gorm:"column:resource_owner"` + UserID string `json:"-" gorm:"column:user_id"` + ApplicationID string `json:"-" gorm:"column:application_id"` + UserAgentID string `json:"-" gorm:"column:user_agent_id"` + Audience pq.StringArray `json:"-" gorm:"column:audience"` + Scopes pq.StringArray `json:"-" gorm:"column:scopes"` + Expiration time.Time `json:"-" gorm:"column:expiration"` + Sequence uint64 `json:"-" gorm:"column:sequence"` + PreferredLanguage string `json:"-" gorm:"column:preferred_language"` } func TokenFromModel(token *model.Token) *Token { return &Token{ - ID: token.ID, - CreationDate: token.CreationDate, - ChangeDate: token.ChangeDate, - ResourceOwner: token.ResourceOwner, - UserID: token.UserID, - ApplicationID: token.ApplicationID, - UserAgentID: token.UserAgentID, - Audience: token.Audience, - Scopes: token.Scopes, - Expiration: token.Expiration, - Sequence: token.Sequence, + ID: token.ID, + CreationDate: token.CreationDate, + ChangeDate: token.ChangeDate, + ResourceOwner: token.ResourceOwner, + UserID: token.UserID, + ApplicationID: token.ApplicationID, + UserAgentID: token.UserAgentID, + Audience: token.Audience, + Scopes: token.Scopes, + Expiration: token.Expiration, + Sequence: token.Sequence, + PreferredLanguage: token.PreferredLanguage, } } func TokenToModel(token *Token) *model.Token { return &model.Token{ - ID: token.ID, - CreationDate: token.CreationDate, - ChangeDate: token.ChangeDate, - ResourceOwner: token.ResourceOwner, - UserID: token.UserID, - ApplicationID: token.ApplicationID, - UserAgentID: token.UserAgentID, - Audience: token.Audience, - Scopes: token.Scopes, - Expiration: token.Expiration, - Sequence: token.Sequence, + ID: token.ID, + CreationDate: token.CreationDate, + ChangeDate: token.ChangeDate, + ResourceOwner: token.ResourceOwner, + UserID: token.UserID, + ApplicationID: token.ApplicationID, + UserAgentID: token.UserAgentID, + Audience: token.Audience, + Scopes: token.Scopes, + Expiration: token.Expiration, + Sequence: token.Sequence, + PreferredLanguage: token.PreferredLanguage, } } diff --git a/internal/token/repository/view/token.go b/internal/token/repository/view/token.go index ac985390a1..5a90ad9e1f 100644 --- a/internal/token/repository/view/token.go +++ b/internal/token/repository/view/token.go @@ -1,6 +1,7 @@ package view import ( + global_model "github.com/caos/zitadel/internal/model" "time" "github.com/jinzhu/gorm" @@ -22,6 +23,20 @@ func TokenByID(db *gorm.DB, table, tokenID string) (*model.Token, error) { return token, err } +func TokensByUserID(db *gorm.DB, table, userID string) ([]*model.Token, error) { + tokens := make([]*model.Token, 0) + userIDQuery := &token_model.TokenSearchQuery{ + Key: token_model.TokenSearchKeyUserID, + Method: global_model.SearchMethodEquals, + Value: userID, + } + query := repository.PrepareSearchQuery(table, model.TokenSearchRequest{ + Queries: []*token_model.TokenSearchQuery{userIDQuery}, + }) + _, err := query(db, &tokens) + return tokens, err +} + func IsTokenValid(db *gorm.DB, table, tokenID string) (bool, error) { token, err := TokenByID(db, table, tokenID) if err == nil { @@ -38,6 +53,15 @@ func PutToken(db *gorm.DB, table string, token *model.Token) error { return save(db, token) } +func PutTokens(db *gorm.DB, table string, tokens ...*model.Token) error { + save := repository.PrepareBulkSave(table) + t := make([]interface{}, len(tokens)) + for i, token := range tokens { + t[i] = token + } + return save(db, t...) +} + func DeleteToken(db *gorm.DB, table, tokenID string) error { delete := repository.PrepareDeleteByKey(table, model.TokenSearchKey(token_model.TokenSearchKeyTokenID), tokenID) return delete(db) diff --git a/internal/tracing/span.go b/internal/tracing/span.go index ec7c46d53d..f059f9f764 100644 --- a/internal/tracing/span.go +++ b/internal/tracing/span.go @@ -2,11 +2,11 @@ package tracing import ( "fmt" + errors2 "github.com/caos/zitadel/internal/api/grpc/errors" "strconv" "go.opencensus.io/trace" - "github.com/caos/zitadel/internal/api/grpc" "github.com/caos/zitadel/internal/errors" ) @@ -40,7 +40,7 @@ func (s *Span) SetStatusByError(err error) { } func statusFromError(err error) trace.Status { - code, msg, _, _ := grpc.ExtractCaosError(err) + code, msg, _, _ := errors2.ExtractCaosError(err) return trace.Status{Code: int32(code), Message: msg} } diff --git a/migrations/cockroach/V1.8__token.sql b/migrations/cockroach/V1.8__token.sql new file mode 100644 index 0000000000..33424018e4 --- /dev/null +++ b/migrations/cockroach/V1.8__token.sql @@ -0,0 +1,2 @@ + +ALTER TABLE auth.tokens ADD COLUMN preferred_language TEXT; \ No newline at end of file