]> git.openstreetmap.org Git - rails.git/commitdiff
Add openid connect support using doorkeeper-openid_connect gem
authorMilan Cvetkovic <mcvetkovic@microsoft.com>
Wed, 30 Aug 2023 12:36:55 +0000 (12:36 +0000)
committerTom Hughes <tom@compton.nu>
Tue, 3 Oct 2023 17:53:47 +0000 (18:53 +0100)
... as discussed in [Issue 507](https://github.com/openstreetmap/operations/issues/507)
and described by @mmd-osm.

To activate, set the value of `doorkeeper_signing_key` to RSA private key.

Allows using openstreetmap as an identity provider.

Adds `openid` scope to OAuth2 authorizations, required to login to OSM.

Currently, the only claims returned are:
 - "openid" scope: "sub" and "preferred_username"
 - "read_email" scope: "email"

app/views/oauth2_applications/_form.html.erb
app/views/oauth2_authorizations/new.html.erb
config/initializers/doorkeeper.rb
config/initializers/doorkeeper_openid_connect.rb
config/locales/en.yml
config/routes.rb
config/settings.yml
lib/oauth.rb

index 7fde3e0e7f19d57aa0ca9b2a4ca0d7a43e49e3b4..51267c069e3e80f486c803093c46ba197fa7d60a 100644 (file)
@@ -3,5 +3,5 @@
 <%= f.form_group :confidential do %>
   <%= f.check_box :confidential %>
 <% end %>
-<%= f.collection_check_boxes :scopes, Oauth.scopes(:privileged => current_user.administrator?), :name, :description %>
+<%= f.collection_check_boxes :scopes, Oauth.scopes(:oauth2 => true, :privileged => current_user.administrator?), :name, :description %>
 <%= f.primary %>
index 971e0e20ad8b7979d85f5c5383036ebd0e989f7b..ac9c7c6c598404131f12fc790444d43b6b21fa36 100644 (file)
@@ -18,6 +18,7 @@
       <%= f.hidden_field :state, :value => @pre_auth.state %>
       <%= f.hidden_field :response_type, :value => @pre_auth.response_type %>
       <%= f.hidden_field :scope, :value => @pre_auth.scope %>
+      <%= f.hidden_field :nonce, :value => @pre_auth.nonce %>
       <%= f.hidden_field :code_challenge, :value => @pre_auth.code_challenge %>
       <%= f.hidden_field :code_challenge_method, :value => @pre_auth.code_challenge_method %>
       <%= f.primary t(".authorize") %>
@@ -30,6 +31,7 @@
       <%= f.hidden_field :state, :value => @pre_auth.state %>
       <%= f.hidden_field :response_type, :value => @pre_auth.response_type %>
       <%= f.hidden_field :scope, :value => @pre_auth.scope %>
+      <%= f.hidden_field :nonce, :value => @pre_auth.nonce %>
       <%= f.hidden_field :code_challenge, :value => @pre_auth.code_challenge %>
       <%= f.hidden_field :code_challenge_method, :value => @pre_auth.code_challenge_method %>
       <%= f.submit t(".deny") %>
index a2df9167f420d37da6f3ab263dbf98efaddc4701..c1d4e2f783127b92ee028c6bebf2c2841efd3dbf 100644 (file)
@@ -225,7 +225,7 @@ Doorkeeper.configure do
   # https://doorkeeper.gitbook.io/guides/ruby-on-rails/scopes
 
   # default_scopes  :public
-  optional_scopes(*Oauth::SCOPES, *Oauth::PRIVILEGED_SCOPES)
+  optional_scopes(*Oauth::SCOPES, *Oauth::PRIVILEGED_SCOPES, *Oauth::OAUTH2_SCOPES)
 
   # Allows to restrict only certain scopes for grant_type.
   # By default, all the scopes will be available for all the grant types.
index e91a907c281e7006e3cc42b5aed25bdf21b091a0..7f409ecbe8d202f7e8d4a0da4bcc36ad2f513a4a 100644 (file)
@@ -2,71 +2,37 @@
 
 Doorkeeper::OpenidConnect.configure do
   issuer do |_resource_owner, _application|
-    "issuer string"
+    "#{Settings.server_protocol}://#{Settings.server_url}"
   end
 
-  signing_key <<~KEY
-    -----BEGIN RSA PRIVATE KEY-----
-    ....
-    -----END RSA PRIVATE KEY-----
-  KEY
+  signing_key Settings.doorkeeper_signing_key
 
   subject_types_supported [:public]
 
   resource_owner_from_access_token do |access_token|
-    # Example implementation:
-    # User.find_by(id: access_token.resource_owner_id)
+    User.find_by(:id => access_token.resource_owner_id)
   end
 
   auth_time_from_resource_owner do |resource_owner|
-    # Example implementation:
-    # resource_owner.current_sign_in_at
+    # empty block necessary as a workaround to missing configuration
+    # when no auth_time claim is provided
   end
 
-  reauthenticate_resource_owner do |resource_owner, return_to|
-    # Example implementation:
-    # store_location_for resource_owner, return_to
-    # sign_out resource_owner
-    # redirect_to new_user_session_url
+  subject do |resource_owner, _application|
+    resource_owner.id
   end
 
-  # Depending on your configuration, a DoubleRenderError could be raised
-  # if render/redirect_to is called at some point before this callback is executed.
-  # To avoid the DoubleRenderError, you could add these two lines at the beginning
-  #  of this callback: (Reference: https://github.com/rails/rails/issues/25106)
-  #   self.response_body = nil
-  #   @_response_body = nil
-  select_account_for_resource_owner do |resource_owner, return_to|
-    # Example implementation:
-    # store_location_for resource_owner, return_to
-    # redirect_to account_select_url
+  protocol do
+    Settings.server_protocol.to_sym
   end
 
-  subject do |resource_owner, application|
-    # Example implementation:
-    # resource_owner.id
+  claims do
+    claim :preferred_username, :scope => :openid do |resource_owner, _scopes, _access_token|
+      resource_owner.display_name
+    end
 
-    # or if you need pairwise subject identifier, implement like below:
-    # Digest::SHA256.hexdigest("#{resource_owner.id}#{URI.parse(application.redirect_uri).host}#{'your_secret_salt'}")
+    claim :email, :scope => :read_email, :response => [:id_token, :user_info] do |resource_owner, _scopes, _access_token|
+      resource_owner.email
+    end
   end
-
-  # Protocol to use when generating URIs for the discovery endpoint,
-  # for example if you also use HTTPS in development
-  # protocol do
-  #   :https
-  # end
-
-  # Expiration time on or after which the ID Token MUST NOT be accepted for processing. (default 120 seconds).
-  # expiration 600
-
-  # Example claims:
-  # claims do
-  #   normal_claim :_foo_ do |resource_owner|
-  #     resource_owner.foo
-  #   end
-
-  #   normal_claim :_bar_ do |resource_owner|
-  #     resource_owner.bar
-  #   end
-  # end
 end
index c9583abce02841b2572dfc382f97c3e91ff1ac38..aca571d53a586a4b2975cde54188a892d2184131 100644 (file)
@@ -2549,6 +2549,7 @@ en:
     permissions:
       missing: "You have not permitted the application access to this facility"
     scopes:
+      openid: Sign-in using OpenStreetMap
       read_prefs: Read user preferences
       write_prefs: Modify user preferences
       write_diary: Create diary entries, comments and make friends
index 5b537ea3e02ae0e4186fbcc38bf75d8ed716c380..43c43a793461242e12541da86ce4531ae43fd7c0 100644 (file)
@@ -1,11 +1,12 @@
 OpenStreetMap::Application.routes.draw do
-  use_doorkeeper_openid_connect
   use_doorkeeper :scope => "oauth2" do
     controllers :authorizations => "oauth2_authorizations",
                 :applications => "oauth2_applications",
                 :authorized_applications => "oauth2_authorized_applications"
   end
 
+  use_doorkeeper_openid_connect :scope => "oauth2" if Settings.key?(:doorkeeper_signing_key)
+
   # API
   namespace :api do
     get "capabilities" => "capabilities#show" # Deprecated, remove when 0.6 support is removed
index 3ea298efcb4378f4073397d02d301d785c772ff2..f30331b0713c9d555c29eec561f2b991600e2dd7 100644 (file)
@@ -150,3 +150,8 @@ smtp_password: null
 #signup_ip_max_burst:
 #signup_email_per_day:
 #signup_email_max_burst:
+# Private key for signing id_tokens
+#doorkeeper_signing_key: |
+#  -----BEGIN PRIVATE KEY-----
+#  ...
+#  -----END PRIVATE KEY-----
index 7ff2ba8b4358428c04814a899d5e8563be0b971b..0456c08732a927eb1c944f74b5ccc969f9971ce4 100644 (file)
@@ -1,6 +1,7 @@
 module Oauth
   SCOPES = %w[read_prefs write_prefs write_diary write_api read_gpx write_gpx write_notes].freeze
   PRIVILEGED_SCOPES = %w[read_email skip_authorization].freeze
+  OAUTH2_SCOPES = %w[openid].freeze
 
   class Scope
     attr_reader :name
@@ -14,9 +15,10 @@ module Oauth
     end
   end
 
-  def self.scopes(privileged: false)
+  def self.scopes(oauth2: false, privileged: false)
     scopes = SCOPES
     scopes += PRIVILEGED_SCOPES if privileged
+    scopes += OAUTH2_SCOPES if oauth2
     scopes.collect { |s| Scope.new(s) }
   end
 end