mirror of
https://github.com/netbirdio/dex.git
synced 2026-05-22 18:43:53 -07:00
feat(gitlab): implement TokenIdentity method (#4606)
Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
This commit is contained in:
@@ -87,8 +87,9 @@ type connectorData struct {
|
||||
}
|
||||
|
||||
var (
|
||||
_ connector.CallbackConnector = (*gitlabConnector)(nil)
|
||||
_ connector.RefreshConnector = (*gitlabConnector)(nil)
|
||||
_ connector.CallbackConnector = (*gitlabConnector)(nil)
|
||||
_ connector.RefreshConnector = (*gitlabConnector)(nil)
|
||||
_ connector.TokenIdentityConnector = (*gitlabConnector)(nil)
|
||||
)
|
||||
|
||||
type gitlabConnector struct {
|
||||
@@ -243,6 +244,34 @@ func (c *gitlabConnector) Refresh(ctx context.Context, s connector.Scopes, ident
|
||||
}
|
||||
}
|
||||
|
||||
// TokenIdentity is used for token exchange, verifying a GitLab access token
|
||||
// and returning the associated user identity. This enables direct authentication
|
||||
// with Dex using an existing GitLab token without going through the OAuth flow.
|
||||
//
|
||||
// Note: The connector decides whether to fetch groups based on its configuration
|
||||
// (groups filter, getGroupsPermission), not on the scopes from the token exchange request.
|
||||
// The server will then decide whether to include groups in the final token based on
|
||||
// the requested scopes. This matches the behavior of other connectors (e.g., OIDC).
|
||||
func (c *gitlabConnector) TokenIdentity(ctx context.Context, _, subjectToken string) (connector.Identity, error) {
|
||||
if c.httpClient != nil {
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
AccessToken: subjectToken,
|
||||
TokenType: "Bearer", // GitLab tokens are typically Bearer tokens even if the type is not explicitly provided.
|
||||
}
|
||||
|
||||
// For token exchange, we determine if groups should be fetched based on connector configuration.
|
||||
// If the connector has groups filter or getGroupsPermission enabled, we fetch groups.
|
||||
scopes := connector.Scopes{
|
||||
// Scopes are not provided in token exchange, so we request groups every time and return only if configured.
|
||||
Groups: true,
|
||||
}
|
||||
|
||||
return c.identity(ctx, scopes, token)
|
||||
}
|
||||
|
||||
func (c *gitlabConnector) groupsRequired(groupScope bool) bool {
|
||||
return len(c.groups) > 0 || groupScope
|
||||
}
|
||||
|
||||
@@ -485,3 +485,88 @@ func expectEquals(t *testing.T, a interface{}, b interface{}) {
|
||||
t.Errorf("Expected %+v to equal %+v", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenIdentity(t *testing.T) {
|
||||
// Note: These tests verify that the connector returns groups based on its configuration.
|
||||
// The actual inclusion of groups in the final Dex token depends on the 'groups' scope
|
||||
// in the token exchange request, which is handled by the Dex server, not the connector.
|
||||
tests := []struct {
|
||||
name string
|
||||
userInfo userInfo
|
||||
groups []string
|
||||
getGroupsPermission bool
|
||||
useLoginAsID bool
|
||||
expectUserID string
|
||||
expectGroups []string
|
||||
}{
|
||||
{
|
||||
name: "without groups config",
|
||||
expectUserID: "12345678",
|
||||
expectGroups: nil,
|
||||
},
|
||||
{
|
||||
name: "with groups filter",
|
||||
userInfo: userInfo{
|
||||
Groups: []string{"team-1", "team-2"},
|
||||
},
|
||||
groups: []string{"team-1"},
|
||||
expectUserID: "12345678",
|
||||
expectGroups: []string{"team-1"},
|
||||
},
|
||||
{
|
||||
name: "with groups permission",
|
||||
userInfo: userInfo{
|
||||
Groups: []string{"ops", "dev"},
|
||||
OwnerPermission: []string{"ops"},
|
||||
DeveloperPermission: []string{"dev"},
|
||||
MaintainerPermission: []string{},
|
||||
},
|
||||
getGroupsPermission: true,
|
||||
expectUserID: "12345678",
|
||||
expectGroups: []string{"ops", "dev", "ops:owner", "dev:developer"},
|
||||
},
|
||||
{
|
||||
name: "with useLoginAsID",
|
||||
useLoginAsID: true,
|
||||
expectUserID: "joebloggs",
|
||||
expectGroups: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
responses := map[string]interface{}{
|
||||
"/api/v4/user": gitlabUser{
|
||||
Email: "some@email.com",
|
||||
ID: 12345678,
|
||||
Name: "Joe Bloggs",
|
||||
Username: "joebloggs",
|
||||
},
|
||||
"/oauth/userinfo": tc.userInfo,
|
||||
}
|
||||
|
||||
s := newTestServer(responses)
|
||||
defer s.Close()
|
||||
|
||||
c := gitlabConnector{
|
||||
baseURL: s.URL,
|
||||
httpClient: newClient(),
|
||||
groups: tc.groups,
|
||||
getGroupsPermission: tc.getGroupsPermission,
|
||||
useLoginAsID: tc.useLoginAsID,
|
||||
}
|
||||
|
||||
accessToken := "test-access-token"
|
||||
ctx := context.Background()
|
||||
identity, err := c.TokenIdentity(ctx, "urn:ietf:params:oauth:token-type:access_token", accessToken)
|
||||
|
||||
expectNil(t, err)
|
||||
expectEquals(t, identity.UserID, tc.expectUserID)
|
||||
expectEquals(t, identity.Username, "Joe Bloggs")
|
||||
expectEquals(t, identity.PreferredUsername, "joebloggs")
|
||||
expectEquals(t, identity.Email, "some@email.com")
|
||||
expectEquals(t, identity.EmailVerified, true)
|
||||
expectEquals(t, identity.Groups, tc.expectGroups)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user