From 3b5df52c0f40852a1cacf26c8849c709f332cf6c Mon Sep 17 00:00:00 2001 From: Pavel Borzenkov Date: Wed, 25 Oct 2017 00:19:18 +0300 Subject: [PATCH] connector/linkedin: implement RefreshConnector interface Do Refresh() by querying user's profile data. Since LinkedIn doesn't provide refresh tokens at all, and the access tokens have 60 days expiration, refresh tokens issued by Dex will fail to update after 60 days. Signed-off-by: Pavel Borzenkov --- connector/linkedin/linkedin.go | 40 ++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/connector/linkedin/linkedin.go b/connector/linkedin/linkedin.go index e6176917..ba85eefc 100644 --- a/connector/linkedin/linkedin.go +++ b/connector/linkedin/linkedin.go @@ -45,15 +45,20 @@ func (c *Config) Open(id string, logger logrus.FieldLogger) (connector.Connector }, nil } +type connectorData struct { + AccessToken string `json:"accessToken"` +} + type linkedInConnector struct { oauth2Config *oauth2.Config logger logrus.FieldLogger } -// LinkedIn doesn't provide refresh tokens, so we don't implement -// RefreshConnector here. +// LinkedIn doesn't provide refresh tokens, so refresh tokens issued by Dex +// will expire in 60 days (default LinkedIn token lifetime). var ( _ connector.CallbackConnector = (*linkedInConnector)(nil) + _ connector.RefreshConnector = (*linkedInConnector)(nil) ) // LoginURL returns an access token request URL @@ -92,9 +97,40 @@ func (c *linkedInConnector) HandleCallback(s connector.Scopes, r *http.Request) EmailVerified: true, } + if s.OfflineAccess { + data := connectorData{AccessToken: token.AccessToken} + connData, err := json.Marshal(data) + if err != nil { + return identity, fmt.Errorf("linkedin: marshal connector data: %v", err) + } + identity.ConnectorData = connData + } + return identity, nil } +func (c *linkedInConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) { + if len(ident.ConnectorData) == 0 { + return ident, fmt.Errorf("linkedin: no upstream access token found") + } + + var data connectorData + if err := json.Unmarshal(ident.ConnectorData, &data); err != nil { + return ident, fmt.Errorf("linkedin: unmarshal access token: %v", err) + } + + client := c.oauth2Config.Client(ctx, &oauth2.Token{AccessToken: data.AccessToken}) + profile, err := c.profile(ctx, client) + if err != nil { + return ident, fmt.Errorf("linkedin: get profile: %v", err) + } + + ident.Username = profile.fullname() + ident.Email = profile.Email + + return ident, nil +} + type profile struct { ID string `json:"id"` FirstName string `json:"firstName"`