From 93575a3f05f4cbf42ef2ddf1998e8ff801a0b216 Mon Sep 17 00:00:00 2001 From: Bikash Pandey Date: Wed, 18 Jun 2025 17:27:21 +0545 Subject: [PATCH 1/5] exp: organizations scope instead of common for authorization --- lib/omniauth/strategies/microsoft_graph.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/omniauth/strategies/microsoft_graph.rb b/lib/omniauth/strategies/microsoft_graph.rb index 3cef1c4..1562dc6 100644 --- a/lib/omniauth/strategies/microsoft_graph.rb +++ b/lib/omniauth/strategies/microsoft_graph.rb @@ -11,8 +11,8 @@ class MicrosoftGraph < OmniAuth::Strategies::OAuth2 option :client_options, { site: 'https://login.microsoftonline.com/', - token_url: 'common/oauth2/v2.0/token', - authorize_url: 'common/oauth2/v2.0/authorize' + token_url: 'organizations/oauth2/v2.0/token', + authorize_url: 'organizations/oauth2/v2.0/authorize' } option :authorize_options, %i[state callback_url access_type display score auth_type scope prompt login_hint domain_hint response_mode] From 1a8b598a18606d7734c508056f602868ce6d4d66 Mon Sep 17 00:00:00 2001 From: Bikash Pandey Date: Thu, 19 Jun 2025 14:38:15 +0545 Subject: [PATCH 2/5] feat: dynamic microsoft graph/yammer profile endpoint to fix invalid audience for yammer --- lib/omniauth/strategies/microsoft_graph.rb | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/omniauth/strategies/microsoft_graph.rb b/lib/omniauth/strategies/microsoft_graph.rb index 1562dc6..a38442e 100644 --- a/lib/omniauth/strategies/microsoft_graph.rb +++ b/lib/omniauth/strategies/microsoft_graph.rb @@ -6,6 +6,8 @@ class MicrosoftGraph < OmniAuth::Strategies::OAuth2 BASE_SCOPE_URL = 'https://graph.microsoft.com/' BASE_SCOPES = %w[offline_access openid email profile].freeze DEFAULT_SCOPE = 'offline_access openid email profile User.Read'.freeze + YAMMER_PROFILE_URL = 'https://www.yammer.com/api/v1/users/current.json' + MICROSOFT_GRAPH_PROFILE_URL = 'https://graph.microsoft.com/v1.0/me' option :name, :microsoft_graph @@ -64,7 +66,7 @@ def authorize_params end def raw_info - @raw_info ||= access_token.get('https://graph.microsoft.com/v1.0/me').parsed + @raw_info ||= access_token.get(profile_endpoint).parsed end def callback_url @@ -73,11 +75,25 @@ def callback_url def custom_build_access_token access_token = get_access_token(request) + # Get the profile(microsoft graph / yammer) endpoint choice based on returned bearer token + @profile_endpoint = determine_profile_endpoint(request) access_token end alias build_access_token custom_build_access_token + def profile_endpoint + @profile_endpoint ||= MICROSOFT_GRAPH_PROFILE_URL + end + + def determine_profile_endpoint(request) + if request.env['omniauth.params']['scope']&.include? 'yammer' + YAMMER_PROFILE_URL + else + MICROSOFT_GRAPH_PROFILE_URL + end + end + private def get_access_token(request) From d8457af2667a03fcf0519ad72a913b24abfce9df Mon Sep 17 00:00:00 2001 From: Bikash Pandey Date: Mon, 23 Jun 2025 13:05:56 +0545 Subject: [PATCH 3/5] Revert "exp: organizations scope instead of common for authorization" This reverts commit 93575a3f05f4cbf42ef2ddf1998e8ff801a0b216. --- lib/omniauth/strategies/microsoft_graph.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/omniauth/strategies/microsoft_graph.rb b/lib/omniauth/strategies/microsoft_graph.rb index a38442e..12f1c30 100644 --- a/lib/omniauth/strategies/microsoft_graph.rb +++ b/lib/omniauth/strategies/microsoft_graph.rb @@ -13,8 +13,8 @@ class MicrosoftGraph < OmniAuth::Strategies::OAuth2 option :client_options, { site: 'https://login.microsoftonline.com/', - token_url: 'organizations/oauth2/v2.0/token', - authorize_url: 'organizations/oauth2/v2.0/authorize' + token_url: 'common/oauth2/v2.0/token', + authorize_url: 'common/oauth2/v2.0/authorize' } option :authorize_options, %i[state callback_url access_type display score auth_type scope prompt login_hint domain_hint response_mode] From 889023ced4a5e5f82df3b7306e07f38c1e318dfb Mon Sep 17 00:00:00 2001 From: Bikash Pandey Date: Tue, 1 Jul 2025 11:35:50 +0545 Subject: [PATCH 4/5] spec: basic assertions for profile endpoints --- .../strategies/microsoft_graph_oauth2_spec.rb | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/spec/omniauth/strategies/microsoft_graph_oauth2_spec.rb b/spec/omniauth/strategies/microsoft_graph_oauth2_spec.rb index b87a84c..01482d5 100644 --- a/spec/omniauth/strategies/microsoft_graph_oauth2_spec.rb +++ b/spec/omniauth/strategies/microsoft_graph_oauth2_spec.rb @@ -457,4 +457,82 @@ end.to raise_error(OAuth2::Error) end end + + describe 'Yammer profile endpoint support' do + describe '#profile_endpoint' do + context 'when no profile endpoint is determined' do + it 'defaults to Microsoft Graph profile URL' do + expect(subject.profile_endpoint).to eq('https://graph.microsoft.com/v1.0/me') + end + end + + context 'when profile endpoint is already set' do + before { subject.instance_variable_set(:@profile_endpoint, 'https://custom.endpoint.com') } + + it 'returns the previously set endpoint' do + expect(subject.profile_endpoint).to eq('https://custom.endpoint.com') + end + end + end + + describe '#determine_profile_endpoint' do + let(:request) { double('Request', env: request_env) } + + context 'when scope includes Yammer access_as_user scope' do + let(:request_env) { { 'omniauth.params' => { 'scope' => 'https://api.yammer.com/access_as_user' } } } + + it 'returns Yammer profile URL' do + expect(subject.determine_profile_endpoint(request)).to eq('https://www.yammer.com/api/v1/users/current.json') + end + end + + context 'when scope includes Yammer user_impersonation scope' do + let(:request_env) { { 'omniauth.params' => { 'scope' => 'openid profile https://api.yammer.com/user_impersonation' } } } + + it 'returns Yammer profile URL' do + expect(subject.determine_profile_endpoint(request)).to eq('https://www.yammer.com/api/v1/users/current.json') + end + end + + context 'when scope includes Yammer scope among other scopes' do + let(:request_env) { { 'omniauth.params' => { 'scope' => 'offline_access openid email profile https://api.yammer.com/access_as_user User.Read' } } } + + it 'returns Yammer profile URL' do + expect(subject.determine_profile_endpoint(request)).to eq('https://www.yammer.com/api/v1/users/current.json') + end + end + + context 'when scope includes multiple Yammer scopes' do + let(:request_env) { { 'omniauth.params' => { 'scope' => 'openid profile https://api.yammer.com/access_as_user https://api.yammer.com/user_impersonation' } } } + + it 'returns Yammer profile URL' do + expect(subject.determine_profile_endpoint(request)).to eq('https://www.yammer.com/api/v1/users/current.json') + end + end + + context 'when scope does not include any Yammer scopes' do + let(:request_env) { { 'omniauth.params' => { 'scope' => 'openid profile User.Read' } } } + + it 'returns Microsoft Graph profile URL' do + expect(subject.determine_profile_endpoint(request)).to eq('https://graph.microsoft.com/v1.0/me') + end + end + + context 'when scope is nil' do + let(:request_env) { { 'omniauth.params' => { 'scope' => nil } } } + + it 'returns Microsoft Graph profile URL' do + expect(subject.determine_profile_endpoint(request)).to eq('https://graph.microsoft.com/v1.0/me') + end + end + + context 'when omniauth.params is nil' do + let(:request_env) { { 'omniauth.params' => nil } } + + it 'returns Microsoft Graph profile URL' do + expect(subject.determine_profile_endpoint(request)).to eq('https://graph.microsoft.com/v1.0/me') + end + end + end + end end From ede580852a1c83f13f1da5629d2f528d1ec6c376 Mon Sep 17 00:00:00 2001 From: Bikash Pandey Date: Wed, 2 Jul 2025 10:26:33 +0545 Subject: [PATCH 5/5] chore: defensive microsoft auth request --- lib/omniauth/strategies/microsoft_graph.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/omniauth/strategies/microsoft_graph.rb b/lib/omniauth/strategies/microsoft_graph.rb index 12f1c30..665df28 100644 --- a/lib/omniauth/strategies/microsoft_graph.rb +++ b/lib/omniauth/strategies/microsoft_graph.rb @@ -87,7 +87,9 @@ def profile_endpoint end def determine_profile_endpoint(request) - if request.env['omniauth.params']['scope']&.include? 'yammer' + scope = request&.env&.dig('omniauth.params', 'scope') + + if scope&.include?('yammer') YAMMER_PROFILE_URL else MICROSOFT_GRAPH_PROFILE_URL