diff --git a/htdocs/core/modules/oauth/google_oauthcallback.php b/htdocs/core/modules/oauth/google_oauthcallback.php index 520da0881e9..55a4394aa37 100644 --- a/htdocs/core/modules/oauth/google_oauthcallback.php +++ b/htdocs/core/modules/oauth/google_oauthcallback.php @@ -136,7 +136,32 @@ if (GETPOST('code')) { // We are coming from oauth provider page. // Result is stored into object managed by class DoliStorage into includes/OAuth/Common/Storage/DoliStorage.php, so into table llx_oauth_token $token = $apiService->requestAccessToken(GETPOST('code'), $state); - // Note: The token contains a lot of information about the user. + // Note: The extraparams has the 'id_token' than contains a lot of information about the user. + $extraparams = $token->getExtraParams(); + $jwt = explode('.', $extraparams['id_token']); + + // Extract the middle part, base64 decode, then json_decode it + if (!empty($jwt[1])) { + $userinfo = json_decode(base64_decode($jwt[1]), true); + + // TODO + // We should make the 5 steps of validation of id_token + // Verify that the ID token is properly signed by the issuer. Google-issued tokens are signed using one of the certificates found at the URI specified in the jwks_uri metadata value of the Discovery document. + // Verify that the value of the iss claim in the ID token is equal to https://accounts.google.com or accounts.google.com. + // Verify that the value of the aud claim in the ID token is equal to your app's client ID. + // Verify that the expiry time (exp claim) of the ID token has not passed. + // If you specified a hd parameter value in the request, verify that the ID token has a hd claim that matches an accepted G Suite hosted domain. + + /* + $useremailuniq = $userinfo['sub']; + $useremail = $userinfo['email']; + $useremailverified = $userinfo['email_verified']; + $username = $userinfo['name']; + $userfamilyname = $userinfo['family_name']; + $usergivenname = $userinfo['given_name']; + $hd = $userinfo['hd']; + */ + } setEventMessages($langs->trans('NewTokenStored'), null, 'mesgs'); diff --git a/htdocs/includes/OAuth/OAuth2/Service/Google.php b/htdocs/includes/OAuth/OAuth2/Service/Google.php index 5ab0f8a474b..8cf6daf9a7e 100644 --- a/htdocs/includes/OAuth/OAuth2/Service/Google.php +++ b/htdocs/includes/OAuth/OAuth2/Service/Google.php @@ -13,194 +13,196 @@ use OAuth\Common\Http\Uri\Uri; class Google extends AbstractService { - /** - * Defined scopes - More scopes are listed here: - * https://developers.google.com/oauthplayground/ - * - * Make a pull request if you need more scopes. - */ + /** + * Defined scopes - More scopes are listed here: + * https://developers.google.com/oauthplayground/ + * + * Make a pull request if you need more scopes. + */ - // Basic - const SCOPE_EMAIL = 'email'; - const SCOPE_PROFILE = 'profile'; + // Basic + const SCOPE_EMAIL = 'email'; + const SCOPE_PROFILE = 'profile'; - const SCOPE_USERINFO_EMAIL = 'https://www.googleapis.com/auth/userinfo.email'; - const SCOPE_USERINFO_PROFILE = 'https://www.googleapis.com/auth/userinfo.profile'; + const SCOPE_USERINFO_EMAIL = 'https://www.googleapis.com/auth/userinfo.email'; + const SCOPE_USERINFO_PROFILE = 'https://www.googleapis.com/auth/userinfo.profile'; - // Google+ - const SCOPE_GPLUS_ME = 'https://www.googleapis.com/auth/plus.me'; - const SCOPE_GPLUS_LOGIN = 'https://www.googleapis.com/auth/plus.login'; - const SCOPE_GPLUS_CIRCLES_READ = 'https://www.googleapis.com/auth/plus.circles.read'; - const SCOPE_GPLUS_CIRCLES_WRITE = 'https://www.googleapis.com/auth/plus.circles.write'; - const SCOPE_GPLUS_STREAM_READ = 'https://www.googleapis.com/auth/plus.stream.read'; - const SCOPE_GPLUS_STREAM_WRITE = 'https://www.googleapis.com/auth/plus.stream.write'; - const SCOPE_GPLUS_MEDIA = 'https://www.googleapis.com/auth/plus.media.upload'; + // Google+ + const SCOPE_GPLUS_ME = 'https://www.googleapis.com/auth/plus.me'; + const SCOPE_GPLUS_LOGIN = 'https://www.googleapis.com/auth/plus.login'; + const SCOPE_GPLUS_CIRCLES_READ = 'https://www.googleapis.com/auth/plus.circles.read'; + const SCOPE_GPLUS_CIRCLES_WRITE = 'https://www.googleapis.com/auth/plus.circles.write'; + const SCOPE_GPLUS_STREAM_READ = 'https://www.googleapis.com/auth/plus.stream.read'; + const SCOPE_GPLUS_STREAM_WRITE = 'https://www.googleapis.com/auth/plus.stream.write'; + const SCOPE_GPLUS_MEDIA = 'https://www.googleapis.com/auth/plus.media.upload'; - // Google Drive - const SCOPE_DOCUMENTSLIST = 'https://docs.google.com/feeds/'; - const SCOPE_SPREADSHEETS = 'https://spreadsheets.google.com/feeds/'; - const SCOPE_GOOGLEDRIVE = 'https://www.googleapis.com/auth/drive'; - const SCOPE_DRIVE_APPS = 'https://www.googleapis.com/auth/drive.appdata'; - const SCOPE_DRIVE_APPS_READ_ONLY = 'https://www.googleapis.com/auth/drive.apps.readonly'; - const SCOPE_GOOGLEDRIVE_FILES = 'https://www.googleapis.com/auth/drive.file'; - const SCOPE_DRIVE_METADATA_READ_ONLY = 'https://www.googleapis.com/auth/drive.metadata.readonly'; - const SCOPE_DRIVE_READ_ONLY = 'https://www.googleapis.com/auth/drive.readonly'; - const SCOPE_DRIVE_SCRIPTS = 'https://www.googleapis.com/auth/drive.scripts'; + // Google Drive + const SCOPE_DOCUMENTSLIST = 'https://docs.google.com/feeds/'; + const SCOPE_SPREADSHEETS = 'https://spreadsheets.google.com/feeds/'; + const SCOPE_GOOGLEDRIVE = 'https://www.googleapis.com/auth/drive'; + const SCOPE_DRIVE_APPS = 'https://www.googleapis.com/auth/drive.appdata'; + const SCOPE_DRIVE_APPS_READ_ONLY = 'https://www.googleapis.com/auth/drive.apps.readonly'; + const SCOPE_GOOGLEDRIVE_FILES = 'https://www.googleapis.com/auth/drive.file'; + const SCOPE_DRIVE_METADATA_READ_ONLY = 'https://www.googleapis.com/auth/drive.metadata.readonly'; + const SCOPE_DRIVE_READ_ONLY = 'https://www.googleapis.com/auth/drive.readonly'; + const SCOPE_DRIVE_SCRIPTS = 'https://www.googleapis.com/auth/drive.scripts'; - // Cloud Print - const SCOPE_CLOUD_PRINT = 'https://www.googleapis.com/auth/cloudprint'; + // Cloud Print + const SCOPE_CLOUD_PRINT = 'https://www.googleapis.com/auth/cloudprint'; - // Adwords - const SCOPE_ADSENSE = 'https://www.googleapis.com/auth/adsense'; - const SCOPE_ADWORDS = 'https://www.googleapis.com/auth/adwords/'; - const SCOPE_GAN = 'https://www.googleapis.com/auth/gan'; // google affiliate network...? + // Adwords + const SCOPE_ADSENSE = 'https://www.googleapis.com/auth/adsense'; + const SCOPE_ADWORDS = 'https://www.googleapis.com/auth/adwords/'; + const SCOPE_GAN = 'https://www.googleapis.com/auth/gan'; // google affiliate network...? - // Google Analytics - const SCOPE_ANALYTICS = 'https://www.googleapis.com/auth/analytics'; - const SCOPE_ANALYTICS_EDIT = 'https://www.googleapis.com/auth/analytics.edit'; - const SCOPE_ANALYTICS_MANAGE_USERS = 'https://www.googleapis.com/auth/analytics.manage.users'; - const SCOPE_ANALYTICS_READ_ONLY = 'https://www.googleapis.com/auth/analytics.readonly'; + // Google Analytics + const SCOPE_ANALYTICS = 'https://www.googleapis.com/auth/analytics'; + const SCOPE_ANALYTICS_EDIT = 'https://www.googleapis.com/auth/analytics.edit'; + const SCOPE_ANALYTICS_MANAGE_USERS = 'https://www.googleapis.com/auth/analytics.manage.users'; + const SCOPE_ANALYTICS_READ_ONLY = 'https://www.googleapis.com/auth/analytics.readonly'; - //Gmail - const SCOPE_GMAIL_MODIFY = 'https://www.googleapis.com/auth/gmail.modify'; - const SCOPE_GMAIL_READONLY = 'https://www.googleapis.com/auth/gmail.readonly'; - const SCOPE_GMAIL_COMPOSE = 'https://www.googleapis.com/auth/gmail.compose'; - const SCOPE_GMAIL_SEND = 'https://www.googleapis.com/auth/gmail.send'; - const SCOPE_GMAIL_INSERT = 'https://www.googleapis.com/auth/gmail.insert'; - const SCOPE_GMAIL_LABELS = 'https://www.googleapis.com/auth/gmail.labels'; - const SCOPE_GMAIL_FULL = 'https://mail.google.com/'; + //Gmail + const SCOPE_GMAIL_MODIFY = 'https://www.googleapis.com/auth/gmail.modify'; + const SCOPE_GMAIL_READONLY = 'https://www.googleapis.com/auth/gmail.readonly'; + const SCOPE_GMAIL_COMPOSE = 'https://www.googleapis.com/auth/gmail.compose'; + const SCOPE_GMAIL_SEND = 'https://www.googleapis.com/auth/gmail.send'; + const SCOPE_GMAIL_INSERT = 'https://www.googleapis.com/auth/gmail.insert'; + const SCOPE_GMAIL_LABELS = 'https://www.googleapis.com/auth/gmail.labels'; + const SCOPE_GMAIL_FULL = 'https://mail.google.com/'; - // Other services - const SCOPE_BOOKS = 'https://www.googleapis.com/auth/books'; - const SCOPE_BLOGGER = 'https://www.googleapis.com/auth/blogger'; - const SCOPE_CALENDAR = 'https://www.googleapis.com/auth/calendar'; - const SCOPE_CALENDAR_READ_ONLY = 'https://www.googleapis.com/auth/calendar.readonly'; - const SCOPE_CONTACT = 'https://www.google.com/m8/feeds/'; - const SCOPE_CONTACTS_RO = 'https://www.googleapis.com/auth/contacts.readonly'; - const SCOPE_CHROMEWEBSTORE = 'https://www.googleapis.com/auth/chromewebstore.readonly'; - const SCOPE_GMAIL = 'https://mail.google.com/mail/feed/atom'; - const SCOPE_GMAIL_IMAP_SMTP = 'https://mail.google.com'; - const SCOPE_PICASAWEB = 'https://picasaweb.google.com/data/'; - const SCOPE_SITES = 'https://sites.google.com/feeds/'; - const SCOPE_URLSHORTENER = 'https://www.googleapis.com/auth/urlshortener'; - const SCOPE_WEBMASTERTOOLS = 'https://www.google.com/webmasters/tools/feeds/'; - const SCOPE_TASKS = 'https://www.googleapis.com/auth/tasks'; + // Other services + const SCOPE_BOOKS = 'https://www.googleapis.com/auth/books'; + const SCOPE_BLOGGER = 'https://www.googleapis.com/auth/blogger'; + const SCOPE_CALENDAR = 'https://www.googleapis.com/auth/calendar'; + const SCOPE_CALENDAR_READ_ONLY = 'https://www.googleapis.com/auth/calendar.readonly'; + const SCOPE_CONTACT = 'https://www.google.com/m8/feeds/'; + const SCOPE_CONTACTS_RO = 'https://www.googleapis.com/auth/contacts.readonly'; + const SCOPE_CHROMEWEBSTORE = 'https://www.googleapis.com/auth/chromewebstore.readonly'; + const SCOPE_GMAIL = 'https://mail.google.com/mail/feed/atom'; + const SCOPE_GMAIL_IMAP_SMTP = 'https://mail.google.com'; + const SCOPE_PICASAWEB = 'https://picasaweb.google.com/data/'; + const SCOPE_SITES = 'https://sites.google.com/feeds/'; + const SCOPE_URLSHORTENER = 'https://www.googleapis.com/auth/urlshortener'; + const SCOPE_WEBMASTERTOOLS = 'https://www.google.com/webmasters/tools/feeds/'; + const SCOPE_TASKS = 'https://www.googleapis.com/auth/tasks'; - // Cloud services - const SCOPE_CLOUDSTORAGE = 'https://www.googleapis.com/auth/devstorage.read_write'; - const SCOPE_CONTENTFORSHOPPING = 'https://www.googleapis.com/auth/structuredcontent'; // what even is this - const SCOPE_USER_PROVISIONING = 'https://apps-apis.google.com/a/feeds/user/'; - const SCOPE_GROUPS_PROVISIONING = 'https://apps-apis.google.com/a/feeds/groups/'; - const SCOPE_NICKNAME_PROVISIONING = 'https://apps-apis.google.com/a/feeds/alias/'; + // Cloud services + const SCOPE_CLOUDSTORAGE = 'https://www.googleapis.com/auth/devstorage.read_write'; + const SCOPE_CONTENTFORSHOPPING = 'https://www.googleapis.com/auth/structuredcontent'; // what even is this + const SCOPE_USER_PROVISIONING = 'https://apps-apis.google.com/a/feeds/user/'; + const SCOPE_GROUPS_PROVISIONING = 'https://apps-apis.google.com/a/feeds/groups/'; + const SCOPE_NICKNAME_PROVISIONING = 'https://apps-apis.google.com/a/feeds/alias/'; - // Old - const SCOPE_ORKUT = 'https://www.googleapis.com/auth/orkut'; - const SCOPE_GOOGLELATITUDE = - 'https://www.googleapis.com/auth/latitude.all.best https://www.googleapis.com/auth/latitude.all.city'; - const SCOPE_OPENID = 'openid'; + // Old + const SCOPE_ORKUT = 'https://www.googleapis.com/auth/orkut'; + const SCOPE_GOOGLELATITUDE = + 'https://www.googleapis.com/auth/latitude.all.best https://www.googleapis.com/auth/latitude.all.city'; + const SCOPE_OPENID = 'openid'; - // YouTube - const SCOPE_YOUTUBE_GDATA = 'https://gdata.youtube.com'; - const SCOPE_YOUTUBE_ANALYTICS_MONETARY = 'https://www.googleapis.com/auth/yt-analytics-monetary.readonly'; - const SCOPE_YOUTUBE_ANALYTICS = 'https://www.googleapis.com/auth/yt-analytics.readonly'; - const SCOPE_YOUTUBE = 'https://www.googleapis.com/auth/youtube'; - const SCOPE_YOUTUBE_READ_ONLY = 'https://www.googleapis.com/auth/youtube.readonly'; - const SCOPE_YOUTUBE_UPLOAD = 'https://www.googleapis.com/auth/youtube.upload'; - const SCOPE_YOUTUBE_PARTNER = 'https://www.googleapis.com/auth/youtubepartner'; - const SCOPE_YOUTUBE_PARTNER_AUDIT = 'https://www.googleapis.com/auth/youtubepartner-channel-audit'; + // YouTube + const SCOPE_YOUTUBE_GDATA = 'https://gdata.youtube.com'; + const SCOPE_YOUTUBE_ANALYTICS_MONETARY = 'https://www.googleapis.com/auth/yt-analytics-monetary.readonly'; + const SCOPE_YOUTUBE_ANALYTICS = 'https://www.googleapis.com/auth/yt-analytics.readonly'; + const SCOPE_YOUTUBE = 'https://www.googleapis.com/auth/youtube'; + const SCOPE_YOUTUBE_READ_ONLY = 'https://www.googleapis.com/auth/youtube.readonly'; + const SCOPE_YOUTUBE_UPLOAD = 'https://www.googleapis.com/auth/youtube.upload'; + const SCOPE_YOUTUBE_PARTNER = 'https://www.googleapis.com/auth/youtubepartner'; + const SCOPE_YOUTUBE_PARTNER_AUDIT = 'https://www.googleapis.com/auth/youtubepartner-channel-audit'; - // Google Glass - const SCOPE_GLASS_TIMELINE = 'https://www.googleapis.com/auth/glass.timeline'; - const SCOPE_GLASS_LOCATION = 'https://www.googleapis.com/auth/glass.location'; + // Google Glass + const SCOPE_GLASS_TIMELINE = 'https://www.googleapis.com/auth/glass.timeline'; + const SCOPE_GLASS_LOCATION = 'https://www.googleapis.com/auth/glass.location'; - // Android Publisher - const SCOPE_ANDROID_PUBLISHER = 'https://www.googleapis.com/auth/androidpublisher'; + // Android Publisher + const SCOPE_ANDROID_PUBLISHER = 'https://www.googleapis.com/auth/androidpublisher'; - // Google Gsuite + // Google Gsuite const SCOPE_ADMIN_DIRECTORY_USER = "https://www.googleapis.com/auth/admin.directory.user"; const SCOPE_ADMIN_DIRECTORY_CUSTOMER = "https://www.googleapis.com/auth/admin.directory.customer"; - protected $accessType = 'online'; + protected $accessType = 'online'; - public function __construct( - CredentialsInterface $credentials, - ClientInterface $httpClient, - TokenStorageInterface $storage, - $scopes = array(), - UriInterface $baseApiUri = null - ) { - parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true); + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true); - if (null === $baseApiUri) { - $this->baseApiUri = new Uri('https://www.googleapis.com/oauth2/v1/'); - } - } + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://www.googleapis.com/oauth2/v1/'); + } + } - public function setAccessType($accessType) - { - if (!in_array($accessType, array('online', 'offline'), true)) { - throw new InvalidAccessTypeException('Invalid accessType, expected either online or offline'); - } - $this->accessType = $accessType; - } + public function setAccessType($accessType) + { + if (!in_array($accessType, array('online', 'offline'), true)) { + throw new InvalidAccessTypeException('Invalid accessType, expected either online or offline'); + } + $this->accessType = $accessType; + } - // LDR CHANGE Add approval_prompt to force the prompt if value is set to 'force' so it force return of a "refresh token" in addition to "standard token" - public $approvalPrompt='auto'; - public function setApprouvalPrompt($prompt) - { - if (!in_array($prompt, array('auto', 'force'), true)) { - // @todo Maybe could we rename this exception - throw new InvalidAccessTypeException('Invalid approuvalPrompt, expected either auto or force.'); - } - $this->approvalPrompt = $prompt; - } + // LDR CHANGE Add approval_prompt to force the prompt if value is set to 'force' so it force return of a "refresh token" in addition to "standard token" + public $approvalPrompt='auto'; + public function setApprouvalPrompt($prompt) + { + if (!in_array($prompt, array('auto', 'force'), true)) { + // @todo Maybe could we rename this exception + throw new InvalidAccessTypeException('Invalid approuvalPrompt, expected either auto or force.'); + } + $this->approvalPrompt = $prompt; + } - /** - * {@inheritdoc} - */ - public function getAuthorizationEndpoint() - { - // LDR CHANGE Add approval_prompt to force the prompt if value is set to 'force' so it force return of a "refresh token" in addition to "standard token" - //return new Uri('https://accounts.google.com/o/oauth2/auth?access_type='.$this->accessType); - return new Uri('https://accounts.google.com/o/oauth2/auth?'.($this->approvalPrompt?'approval_prompt='.$this->approvalPrompt.'&':'').'access_type='.$this->accessType); - } + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + // LDR CHANGE Add approval_prompt to force the prompt if value is set to 'force' so it force return of a "refresh token" in addition to "standard token" + //return new Uri('https://accounts.google.com/o/oauth2/auth?access_type='.$this->accessType); + $url = 'https://accounts.google.com/o/oauth2/auth?nonce='.bin2hex(random_bytes(64/8)).'&'.($this->approvalPrompt?'approval_prompt='.$this->approvalPrompt.'&':'').'access_type='.$this->accessType; + // TODO Add param hd and/or login_hint + return new Uri($url); + } - /** - * {@inheritdoc} - */ - public function getAccessTokenEndpoint() - { - return new Uri('https://accounts.google.com/o/oauth2/token'); - } + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://accounts.google.com/o/oauth2/token'); + } - /** - * {@inheritdoc} - */ - protected function parseAccessTokenResponse($responseBody) - { - $data = json_decode($responseBody, true); + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); - if (null === $data || !is_array($data)) { - throw new TokenResponseException('Unable to parse response.'); - } elseif (isset($data['error'])) { - throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); - } + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } - $token = new StdOAuth2Token(); - $token->setAccessToken($data['access_token']); - $token->setLifetime($data['expires_in']); + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); - if (isset($data['refresh_token'])) { - $token->setRefreshToken($data['refresh_token']); - unset($data['refresh_token']); - } + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } - unset($data['access_token']); - unset($data['expires_in']); + unset($data['access_token']); + unset($data['expires_in']); - $token->setExtraParams($data); + $token->setExtraParams($data); - return $token; - } + return $token; + } }