-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update GitLab IDP to use OIDC instead of OAuth2
This change updates the GitLab IDP integration to use OpenID Connect instead of OAuth2. This has the benefit of using a standard that was meant for authentication, which allows us to remove the GitLab specific code from our integration. We can simply reuse the OIDC code with a GitLab specific configuration. From the perspective of the end user, nothing changes. The configuration of the IDP remains exactly the same and the identity/user objects are unchanged. The only difference is that a very recent version of GitLab is required: version 11.1.0 or later. A positive side effect from this change is that it allows us to use the openid scope instead of the api scope. This means that OpenShift only gets limited read access to perform authentication instead of full read/write access to the user's GitLab account. The user may have to reauthenticate OpenShift's OAuth client on first login. In the future this change will make it easy to pull group membership information from GitLab (the tokens already include a groups claim, we simply need to add code in OpenShift that uses it). Trello: https://trello.com/c/DXntmEOV Signed-off-by: Monis Khan <[email protected]>
- Loading branch information
Showing
3 changed files
with
41 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,135 +1,69 @@ | ||
package gitlab | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"net/url" | ||
"path" | ||
|
||
"github.com/RangelReale/osincli" | ||
"github.com/golang/glog" | ||
|
||
authapi "github.com/openshift/origin/pkg/oauthserver/api" | ||
"github.com/openshift/origin/pkg/oauthserver/oauth/external" | ||
"github.com/openshift/origin/pkg/oauthserver/oauth/external/openid" | ||
) | ||
|
||
const ( | ||
// Uses the GitLab User-API (http://doc.gitlab.com/ce/api/users.html#current-user) | ||
// and OAuth-Provider (http://doc.gitlab.com/ce/integration/oauth_provider.html) | ||
// with default OAuth scope (http://doc.gitlab.com/ce/api/users.html#current-user) | ||
// Requires GitLab 7.7.0 or higher | ||
// https://gitlab.com/help/integration/openid_connect_provider.md | ||
// Uses GitLab OIDC, requires GitLab 11.1.0 or higher | ||
// Earlier versions do not work: https://gitlab.com/gitlab-org/gitlab-ce/issues/47791#note_81269161 | ||
gitlabAuthorizePath = "/oauth/authorize" | ||
gitlabTokenPath = "/oauth/token" | ||
gitlabUserAPIPath = "/api/v3/user" | ||
gitlabOAuthScope = "api" | ||
gitlabUserInfoPath = "/oauth/userinfo" | ||
|
||
// https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/doorkeeper.en.yml | ||
// Authenticate using OpenID Connect | ||
// The ability to authenticate using GitLab, and read-only access to the user's profile information and group memberships | ||
gitlabOIDCScope = "openid" | ||
|
||
// An opaque token that uniquely identifies the user | ||
// Along with providerName, builds the identity object's Name field (see Identity.ProviderUserName) | ||
gitlabIDClaim = "sub" | ||
// The user's GitLab username | ||
// Used as the Name field of the user object (stored in Identity.Extra, see IdentityPreferredUsernameKey) | ||
gitlabPreferredUsernameClaim = "nickname" | ||
// The user's public email address | ||
// The value can optionally be used during manual provisioning (stored in Identity.Extra, see IdentityEmailKey) | ||
gitlabEmailClaim = "email" | ||
// The user's full name | ||
// Used as the FullName field of the user object (stored in Identity.Extra, see IdentityDisplayNameKey) | ||
gitlabDisplayNameClaim = "name" | ||
) | ||
|
||
type provider struct { | ||
providerName string | ||
transport http.RoundTripper | ||
authorizeURL string | ||
tokenURL string | ||
userAPIURL string | ||
clientID string | ||
clientSecret string | ||
} | ||
|
||
type gitlabUser struct { | ||
ID uint64 | ||
Username string | ||
Email string | ||
Name string | ||
} | ||
|
||
func NewProvider(providerName string, transport http.RoundTripper, URL, clientID, clientSecret string) (external.Provider, error) { | ||
func NewProvider(providerName, URL, clientID, clientSecret string, transport http.RoundTripper) (external.Provider, error) { | ||
// Create service URLs | ||
u, err := url.Parse(URL) | ||
if err != nil { | ||
return nil, errors.New("Host URL is invalid") | ||
return nil, errors.New("gitlab host URL is invalid") | ||
} | ||
|
||
return &provider{ | ||
providerName: providerName, | ||
transport: transport, | ||
authorizeURL: appendPath(*u, gitlabAuthorizePath), | ||
tokenURL: appendPath(*u, gitlabTokenPath), | ||
userAPIURL: appendPath(*u, gitlabUserAPIPath), | ||
clientID: clientID, | ||
clientSecret: clientSecret, | ||
}, nil | ||
} | ||
config := openid.Config{ | ||
ClientID: clientID, | ||
ClientSecret: clientSecret, | ||
|
||
func appendPath(u url.URL, subpath string) string { | ||
u.Path = path.Join(u.Path, subpath) | ||
return u.String() | ||
} | ||
AuthorizeURL: appendPath(*u, gitlabAuthorizePath), | ||
TokenURL: appendPath(*u, gitlabTokenPath), | ||
UserInfoURL: appendPath(*u, gitlabUserInfoPath), | ||
|
||
func (p *provider) GetTransport() (http.RoundTripper, error) { | ||
return p.transport, nil | ||
} | ||
Scopes: []string{gitlabOIDCScope}, | ||
|
||
// NewConfig implements external/interfaces/Provider.NewConfig | ||
func (p *provider) NewConfig() (*osincli.ClientConfig, error) { | ||
config := &osincli.ClientConfig{ | ||
ClientId: p.clientID, | ||
ClientSecret: p.clientSecret, | ||
ErrorsInStatusCode: true, | ||
SendClientSecretInParams: true, | ||
AuthorizeUrl: p.authorizeURL, | ||
TokenUrl: p.tokenURL, | ||
Scope: gitlabOAuthScope, | ||
IDClaims: []string{gitlabIDClaim}, | ||
PreferredUsernameClaims: []string{gitlabPreferredUsernameClaim}, | ||
EmailClaims: []string{gitlabEmailClaim}, | ||
NameClaims: []string{gitlabDisplayNameClaim}, | ||
} | ||
return config, nil | ||
} | ||
|
||
// AddCustomParameters implements external/interfaces/Provider.AddCustomParameters | ||
func (p *provider) AddCustomParameters(req *osincli.AuthorizeRequest) { | ||
return openid.NewProvider(providerName, transport, config) | ||
} | ||
|
||
// GetUserIdentity implements external/interfaces/Provider.GetUserIdentity | ||
func (p *provider) GetUserIdentity(data *osincli.AccessData) (authapi.UserIdentityInfo, bool, error) { | ||
req, _ := http.NewRequest("GET", p.userAPIURL, nil) | ||
req.Header.Set("Authorization", fmt.Sprintf("bearer %s", data.AccessToken)) | ||
|
||
client := http.DefaultClient | ||
if p.transport != nil { | ||
client = &http.Client{Transport: p.transport} | ||
} | ||
res, err := client.Do(req) | ||
if err != nil { | ||
return nil, false, err | ||
} | ||
defer res.Body.Close() | ||
|
||
body, err := ioutil.ReadAll(res.Body) | ||
if err != nil { | ||
return nil, false, err | ||
} | ||
|
||
userdata := gitlabUser{} | ||
err = json.Unmarshal(body, &userdata) | ||
if err != nil { | ||
return nil, false, err | ||
} | ||
|
||
if userdata.ID == 0 { | ||
return nil, false, errors.New("Could not retrieve GitLab id") | ||
} | ||
|
||
identity := authapi.NewDefaultUserIdentityInfo(p.providerName, fmt.Sprintf("%d", userdata.ID)) | ||
if len(userdata.Name) > 0 { | ||
identity.Extra[authapi.IdentityDisplayNameKey] = userdata.Name | ||
} | ||
if len(userdata.Username) > 0 { | ||
identity.Extra[authapi.IdentityPreferredUsernameKey] = userdata.Username | ||
} | ||
if len(userdata.Email) > 0 { | ||
identity.Extra[authapi.IdentityEmailKey] = userdata.Email | ||
} | ||
glog.V(4).Infof("Got identity=%#v", identity) | ||
|
||
return identity, true, nil | ||
func appendPath(u url.URL, subpath string) string { | ||
u.Path = path.Join(u.Path, subpath) | ||
return u.String() | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters