omniauth-azure-devops 1.0.8 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b49d7b12fbd7a19f157141da1e8b5bcb79a5e99eb4f315add5b4089e36edc579
4
- data.tar.gz: 7b148fc02e6b8313e2ba3d29b6d6ff773b161ddd077684933d825347d436c6ae
3
+ metadata.gz: 1371aaeba34d0fdb8a1254358ab1a72cfd2e204cd042a3b7fd0395d8299e04f7
4
+ data.tar.gz: 38225a347250640a66ca6c1ccecddbf0c1bbae6e24f3bd9a8b363685ef22dced
5
5
  SHA512:
6
- metadata.gz: 0006ead27889ce6ea0b472d4c0f2b632ca994a6b078664afb9bfa59262faa6d3c1109e5ca50f606be2619962520a9456f828c25c04156d6f1bc1f4f09b6ef60a
7
- data.tar.gz: cbfe15cc8df7aa199ce1562ad2441de69e55ae22535cf2480f1894fe1e3043ee33bd1c552793bdabc815c487bb3d8cd2a4d4295404e98c7985823151f95429a9
6
+ metadata.gz: 10db990de46c448121adbc0873ae5fe8cae0b4d8920015a4c516f134bc6027c41c613ad1f17650a46eef8b77550d73cb0d827d7cf5ae6b91dda3153042496d6a
7
+ data.tar.gz: 8f8c865fc1e4d56970eb21c77232a019492bf40b358963565bdef7ac99b1cd30cfe3a3f3af92339c1093aea67e275f4eb1e3f9a525ba709c44f22d154380ddca
data/.rubocop.yml CHANGED
@@ -46,6 +46,9 @@ Metrics/PerceivedComplexity:
46
46
  Enabled: false
47
47
 
48
48
  # Naming cop configuration
49
+ Naming/FileName:
50
+ Exclude:
51
+ - lib/omniauth-azure-devops.rb
49
52
  Naming/MethodParameterName:
50
53
  Enabled: true
51
54
  Exclude:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.0]
4
+
5
+ - Add `extra.tenant_name` to the `azure_devops_entra` strategy: the Microsoft Entra tenant's display name, read from Microsoft Graph `/organization` with a `User.Read` token redeemed from the sign-in's refresh token. It is `nil` when Graph can't be reached or `User.Read` isn't consented, and never fails the sign-in.
6
+
7
+ ## [1.1.0]
8
+
9
+ - Add the `azure_devops_entra` strategy: Microsoft Entra ID authorization code + PKCE with a certificate-signed client assertion, alongside the unchanged legacy `azure_devops` strategy
10
+ - Require `omniauth-oauth2` `~> 1.7` (PKCE) and `oauth2` `~> 2.0` (`private_key_jwt` client auth and snake_cased responses), and declare the `jwt` dependency
11
+
3
12
  ## [1.0.8]
4
13
 
5
14
  - Address Dependabot security advisories in transitive dependencies by updating `Gemfile.lock`:
data/Gemfile.lock CHANGED
@@ -1,9 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- omniauth-azure-devops (1.0.8)
4
+ omniauth-azure-devops (1.2.0)
5
+ jwt (>= 2.10, < 4)
6
+ oauth2 (~> 2.0)
5
7
  omniauth (>= 1, < 3)
6
- omniauth-oauth2 (~> 1.1)
8
+ omniauth-oauth2 (~> 1.7)
7
9
 
8
10
  GEM
9
11
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -34,6 +34,22 @@ bundle install
34
34
 
35
35
  5. Access user information and tokens as needed in your application by utilizing the OmniAuth authentication data.
36
36
 
37
+ ### Microsoft Entra ID
38
+
39
+ The `azure_devops_entra` strategy signs users in through a multi-tenant Microsoft Entra ID application (authorization code + PKCE) and authenticates the code exchange with a certificate-signed client assertion, so no client secret is used.
40
+
41
+ ```ruby
42
+ provider :azure_devops_entra, ENV['ENTRA_APP_ID'], nil,
43
+ client_private_key: ENV['ENTRA_CLIENT_PRIVATE_KEY'],
44
+ certificate_thumbprint: ENV['ENTRA_CERTIFICATE_THUMBPRINT'],
45
+ scope: '499b84ac-1321-427f-aa17-267ca6975798/.default offline_access',
46
+ callback_path: '/link/azure_devops_entra/oauth_callback'
47
+ ```
48
+
49
+ - `client_private_key` is the PEM private key of the certificate registered on the Entra application, and `certificate_thumbprint` is its base64url-encoded SHA-1 thumbprint (the JWT `x5t` header).
50
+ - `scope` is sent exactly as given. Include `offline_access`, or Entra issues no refresh token.
51
+ - The auth hash `uid` is the Azure DevOps profile id, the same value the `azure_devops` strategy returns. `extra.tenant_id` is the user's Entra tenant id, which is needed to refresh the token later at `https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token`.
52
+
37
53
  ## Running Tests
38
54
 
39
55
  You can run the test suite using the following command:
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAuth
4
4
  module AzureDevops
5
- VERSION = '1.0.8'
5
+ VERSION = '1.2.0'
6
6
  end
7
7
  end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'jwt'
5
+ require 'openssl'
6
+ require 'securerandom'
7
+ require 'omniauth/strategies/oauth2'
8
+
9
+ module OmniAuth
10
+ module Strategies
11
+ class AzureDevopsEntra < OmniAuth::Strategies::OAuth2
12
+ TOKEN_URL = 'https://login.microsoftonline.com/organizations/oauth2/v2.0/token'
13
+ CLIENT_ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
14
+ CLIENT_ASSERTION_LIFETIME_SECONDS = 120
15
+ GRAPH_SCOPE = 'https://graph.microsoft.com/User.Read'
16
+ GRAPH_ORGANIZATION_URL = 'https://graph.microsoft.com/v1.0/organization'
17
+
18
+ option :name, :azure_devops_entra
19
+ option :pkce, true
20
+ option :client_private_key, nil
21
+ option :certificate_thumbprint, nil
22
+
23
+ option :client_options, {
24
+ site: 'https://app.vssps.visualstudio.com',
25
+ authorize_url: 'https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize',
26
+ token_url: TOKEN_URL,
27
+ auth_scheme: :private_key_jwt
28
+ }
29
+
30
+ option :authorize_params, {
31
+ prompt: 'select_account'
32
+ }
33
+
34
+ # Must stay the ADO profile id, never the token's `oid` claim: they are different GUIDs, and rewind-app finds the
35
+ # existing service instance by `platform_id: uid`, so an `oid` here silently creates a second instance.
36
+ uid { raw_info['id'] }
37
+
38
+ info do
39
+ {
40
+ name: raw_info['display_name'],
41
+ email_address: raw_info['email_address']
42
+ }
43
+ end
44
+
45
+ extra do
46
+ {
47
+ raw_info: raw_info,
48
+ tenant_id: tenant_id,
49
+ tenant_name: tenant_name
50
+ }
51
+ end
52
+
53
+ def raw_info
54
+ @raw_info ||= access_token.get('/_apis/profile/profiles/me', params: { 'api-version' => '7.1' }).parsed
55
+ end
56
+
57
+ def tenant_id
58
+ @tenant_id ||= JWT.decode(access_token.token, nil, false).first['tid']
59
+ end
60
+
61
+ def tenant_name
62
+ return @tenant_name if defined?(@tenant_name)
63
+
64
+ @tenant_name = JSON.parse(graph_access_token.get(GRAPH_ORGANIZATION_URL).body).dig('value', 0, 'displayName')
65
+ rescue StandardError => e
66
+ status = e.response&.status if e.is_a?(::OAuth2::Error)
67
+ log :warn, "Could not read the Entra tenant name for tenant #{tenant_id}: #{e.class} (status #{status.inspect}): #{e.message}"
68
+ @tenant_name = nil
69
+ end
70
+
71
+ def token_params
72
+ super.tap do |params|
73
+ params[:client_id] = client.id
74
+ params[:client_assertion_type] = CLIENT_ASSERTION_TYPE
75
+ params[:client_assertion] = client_assertion
76
+ end
77
+ end
78
+
79
+ def callback_url
80
+ full_host + callback_path
81
+ end
82
+
83
+ private
84
+
85
+ def graph_access_token
86
+ client.get_token(
87
+ grant_type: 'refresh_token',
88
+ refresh_token: access_token.refresh_token,
89
+ scope: GRAPH_SCOPE,
90
+ client_id: client.id,
91
+ client_assertion_type: CLIENT_ASSERTION_TYPE,
92
+ client_assertion: client_assertion
93
+ )
94
+ end
95
+
96
+ def client_assertion
97
+ now = Time.now.to_i
98
+ claims = {
99
+ iss: client.id,
100
+ sub: client.id,
101
+ aud: TOKEN_URL,
102
+ jti: SecureRandom.uuid,
103
+ exp: now + CLIENT_ASSERTION_LIFETIME_SECONDS,
104
+ nbf: now,
105
+ iat: now
106
+ }
107
+
108
+ JWT.encode(claims, OpenSSL::PKey::RSA.new(options.client_private_key), 'RS256',
109
+ { alg: 'RS256', typ: 'JWT', x5t: options.certificate_thumbprint })
110
+ end
111
+ end
112
+ end
113
+ end
@@ -2,3 +2,4 @@
2
2
 
3
3
  require 'omni_auth/azure_devops/version'
4
4
  require 'omni_auth/strategies/azure_devops'
5
+ require 'omni_auth/strategies/azure_devops_entra'
@@ -28,6 +28,8 @@ Gem::Specification.new do |gem|
28
28
 
29
29
  gem.required_ruby_version = '>= 3.4'
30
30
 
31
+ gem.add_dependency 'jwt', '>= 2.10', '< 4'
32
+ gem.add_dependency 'oauth2', '~> 2.0'
31
33
  gem.add_dependency 'omniauth', '>= 1', '< 3'
32
- gem.add_dependency 'omniauth-oauth2', '~> 1.1'
34
+ gem.add_dependency 'omniauth-oauth2', '~> 1.7'
33
35
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth-azure-devops
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.8
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rewind Software Inc. | Parth Chandgadhiya
@@ -9,6 +9,40 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: jwt
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '2.10'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '4'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '2.10'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '4'
32
+ - !ruby/object:Gem::Dependency
33
+ name: oauth2
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '2.0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '2.0'
12
46
  - !ruby/object:Gem::Dependency
13
47
  name: omniauth
14
48
  requirement: !ruby/object:Gem::Requirement
@@ -35,14 +69,14 @@ dependencies:
35
69
  requirements:
36
70
  - - "~>"
37
71
  - !ruby/object:Gem::Version
38
- version: '1.1'
72
+ version: '1.7'
39
73
  type: :runtime
40
74
  prerelease: false
41
75
  version_requirements: !ruby/object:Gem::Requirement
42
76
  requirements:
43
77
  - - "~>"
44
78
  - !ruby/object:Gem::Version
45
- version: '1.1'
79
+ version: '1.7'
46
80
  description: An Azure Devops OAuth strategy for OmniAuth 2.0
47
81
  email:
48
82
  - team@rewind.io
@@ -64,6 +98,7 @@ files:
64
98
  - bin/setup
65
99
  - lib/omni_auth/azure_devops/version.rb
66
100
  - lib/omni_auth/strategies/azure_devops.rb
101
+ - lib/omni_auth/strategies/azure_devops_entra.rb
67
102
  - lib/omniauth-azure-devops.rb
68
103
  - omniauth-azure-devops.gemspec
69
104
  homepage: https://github.com/rewindio/omniauth-azure-devops