mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 05:27:33 +00:00
fix: migrate external id of federated users (#6312)
* feat: migrate external id * implement tests and some renaming * fix projection * cleanup * i18n * fix event type * handle migration for new services as well * typo
This commit is contained in:
@@ -15,7 +15,8 @@ import (
|
||||
const (
|
||||
authURLTemplate string = "https://login.microsoftonline.com/%s/oauth2/v2.0/authorize"
|
||||
tokenURLTemplate string = "https://login.microsoftonline.com/%s/oauth2/v2.0/token"
|
||||
userinfoURL string = "https://graph.microsoft.com/v1.0/me"
|
||||
userURL string = "https://graph.microsoft.com/v1.0/me"
|
||||
userinfoEndpoint string = "https://graph.microsoft.com/oidc/userinfo"
|
||||
|
||||
ScopeUserRead string = "User.Read"
|
||||
)
|
||||
@@ -87,7 +88,7 @@ func New(name, clientID, clientSecret, redirectURI string, scopes []string, opts
|
||||
rp, err := oauth.New(
|
||||
config,
|
||||
name,
|
||||
userinfoURL,
|
||||
userURL,
|
||||
func() idp.User {
|
||||
return &User{isEmailVerified: provider.emailVerified}
|
||||
},
|
||||
|
29
internal/idp/providers/azuread/session.go
Normal file
29
internal/idp/providers/azuread/session.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package azuread
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
|
||||
)
|
||||
|
||||
// Session extends the [oauth.Session] to extend it with the [idp.SessionSupportsMigration] functionality
|
||||
type Session struct {
|
||||
*oauth.Session
|
||||
}
|
||||
|
||||
// RetrievePreviousID implements the [idp.SessionSupportsMigration] interface by returning the `sub` from the userinfo endpoint
|
||||
func (s *Session) RetrievePreviousID() (string, error) {
|
||||
req, err := http.NewRequest("GET", userinfoEndpoint, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("authorization", s.Tokens.TokenType+" "+s.Tokens.AccessToken)
|
||||
userinfo := new(oidc.UserInfo)
|
||||
if err := httphelper.HttpRequest(s.Provider.HttpClient(), req, &userinfo); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return userinfo.Subject, nil
|
||||
}
|
@@ -247,12 +247,12 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
provider, err := New(tt.fields.name, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.options...)
|
||||
require.NoError(t, err)
|
||||
|
||||
session := &oauth.Session{
|
||||
session := &Session{Session: &oauth.Session{
|
||||
AuthURL: tt.fields.authURL,
|
||||
Code: tt.fields.code,
|
||||
Tokens: tt.fields.tokens,
|
||||
Provider: provider.Provider,
|
||||
}
|
||||
}}
|
||||
|
||||
user, err := session.FetchUser(context.Background())
|
||||
if tt.want.err != nil && !tt.want.err(err) {
|
||||
@@ -294,3 +294,116 @@ func userinfo() *User {
|
||||
UserPrincipalName: "username",
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_RetrievePreviousID(t *testing.T) {
|
||||
type fields struct {
|
||||
name string
|
||||
clientID string
|
||||
clientSecret string
|
||||
redirectURI string
|
||||
scopes []string
|
||||
httpMock func()
|
||||
tokens *oidc.Tokens[*oidc.IDTokenClaims]
|
||||
}
|
||||
type res struct {
|
||||
id string
|
||||
err bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "invalid token",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
clientSecret: "clientSecret",
|
||||
redirectURI: "redirectURI",
|
||||
httpMock: func() {
|
||||
gock.New("https://graph.microsoft.com").
|
||||
Get("/oidc/userinfo").
|
||||
Reply(401)
|
||||
},
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
},
|
||||
IDTokenClaims: oidc.NewIDTokenClaims(
|
||||
"https://login.microsoftonline.com/consumers/oauth2/v2.0",
|
||||
"sub",
|
||||
[]string{"clientID"},
|
||||
time.Now().Add(1*time.Hour),
|
||||
time.Now().Add(-1*time.Second),
|
||||
"nonce",
|
||||
"",
|
||||
nil,
|
||||
"clientID",
|
||||
0,
|
||||
),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
clientSecret: "clientSecret",
|
||||
redirectURI: "redirectURI",
|
||||
httpMock: func() {
|
||||
gock.New("https://graph.microsoft.com").
|
||||
Get("/oidc/userinfo").
|
||||
Reply(200).
|
||||
JSON(`{"sub":"sub"}`)
|
||||
},
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
},
|
||||
IDTokenClaims: oidc.NewIDTokenClaims(
|
||||
"https://login.microsoftonline.com/consumers/oauth2/v2.0",
|
||||
"sub",
|
||||
[]string{"clientID"},
|
||||
time.Now().Add(1*time.Hour),
|
||||
time.Now().Add(-1*time.Second),
|
||||
"nonce",
|
||||
"",
|
||||
nil,
|
||||
"clientID",
|
||||
0,
|
||||
),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "sub",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer gock.Off()
|
||||
tt.fields.httpMock()
|
||||
a := assert.New(t)
|
||||
|
||||
provider, err := New(tt.fields.name, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes)
|
||||
require.NoError(t, err)
|
||||
session := &Session{Session: &oauth.Session{
|
||||
Tokens: tt.fields.tokens,
|
||||
Provider: provider.Provider,
|
||||
}}
|
||||
|
||||
id, err := session.RetrievePreviousID()
|
||||
if tt.res.err {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
a.Equal(tt.res.id, id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user