Commit 60498a0

mo <mo@mokhan.ca>
2018-10-15 17:12:55
record code_challenge and code_challenge_method
1 parent 18b403b
app/controllers/oauths_controller.rb
@@ -20,29 +20,22 @@ class OauthsController < ApplicationController
       )
     end
 
-    session[:oauth] = {
-      client_id: secure_params[:client_id],
-      response_type: secure_params[:response_type],
-      state: secure_params[:state],
-    }
+    session[:oauth] = secure_params.to_h
   end
 
   def create(oauth = session[:oauth])
     return render_error(:bad_request) if oauth.nil?
 
     client = Client.find_by!(uuid: oauth[:client_id])
-    redirect_to client.redirect_url_for(
-      current_user,
-      oauth[:response_type],
-      oauth[:state]
-    )
-  rescue StandardError
+    redirect_to client.redirect_url_for(current_user, oauth)
+  rescue StandardError => error
+    logger.error(error)
     redirect_to client.redirect_url(error: :invalid_request)
   end
 
   private
 
   def secure_params
-    params.permit(:client_id, :response_type, :redirect_uri, :state)
+    params.permit(:client_id, :response_type, :redirect_uri, :state, :code_challenge, :code_challenge_method)
   end
 end
app/models/authorization.rb
@@ -6,6 +6,7 @@ class Authorization < ApplicationRecord
   belongs_to :user
   belongs_to :client
   has_many :tokens
+  enum challenge_method: { plain: 0, sha256: 1 }
 
   scope :active, -> { where.not(id: revoked.or(where(id: expired))) }
   scope :revoked, -> { where('revoked_at < ?', Time.now) }
app/models/client.rb
@@ -36,8 +36,16 @@ class Client < ApplicationRecord
     RESPONSE_TYPES.include?(response_type)
   end
 
-  def redirect_url_for(user, response_type, state)
-    authorization = authorizations.create!(user: user)
+  def redirect_url_for(user, oauth)
+    response_type = oauth[:response_type]
+    state = oauth[:state]
+
+    authorization = authorizations.create!(
+      user: user,
+      challenge: oauth[:code_challenge],
+      challenge_method: oauth[:code_challenge_method] == 'S256' ? :sha256 : :plain
+    )
+
     if response_type == 'code'
       redirect_url(code: authorization.code, state: state)
     elsif response_type == 'token'
db/migrate/20180905020708_create_authorizations.rb
@@ -6,6 +6,8 @@ class CreateAuthorizations < ActiveRecord::Migration[5.2]
       t.references :user, foreign_key: true
       t.references :client, foreign_key: true
       t.string :code, null: false, index: true
+      t.string :challenge
+      t.integer :challenge_method, default: 0
       t.datetime :expired_at, null: false
       t.datetime :revoked_at
 
db/schema.rb
@@ -38,6 +38,8 @@ ActiveRecord::Schema.define(version: 2018_09_23_234502) do
     t.integer "user_id"
     t.integer "client_id"
     t.string "code", null: false
+    t.string "challenge"
+    t.integer "challenge_method", default: 0
     t.datetime "expired_at", null: false
     t.datetime "revoked_at"
     t.datetime "created_at", null: false
spec/requests/oauth_spec.rb
@@ -79,20 +79,23 @@ RSpec.describe '/oauth' do
         context "when the client requested a token using a valid PKCE with S256" do
           let(:token) { Token.access.active.last&.to_jwt }
           let(:code_verifier) { SecureRandom.hex(128) }
+          let(:code_challenge) { Base64.urlsafe_encode64(Digest::SHA256.hexdigest(code_verifier)) }
 
           before :each do
             get "/oauth", params: {
               client_id: client.to_param,
               response_type: 'code',
-              code_challenge: Base64.urlsafe_encode64(Digest::SHA256.hexdigest(code_verifier)),
+              code_challenge: code_challenge,
               code_challenge_method: 'S256',
               state: state,
               redirect_uri: client.redirect_uri
             }
-            post "/oauth", params: { code_verifier: code_verifier }
+            post "/oauth"
           end
 
           specify { expect(response).to redirect_to(client.redirect_url(code: Authorization.last.code, state: state)) }
+          specify { expect(Authorization.last).to be_sha256 }
+          specify { expect(Authorization.last.challenge).to eql(code_challenge) }
         end
 
         context "when the client requested a token using a valid PKCE with plain" do
@@ -108,10 +111,12 @@ RSpec.describe '/oauth' do
               state: state,
               redirect_uri: client.redirect_uri
             }
-            post "/oauth", params: { code_verifier: code_verifier }
+            post "/oauth"
           end
 
           specify { expect(response).to redirect_to(client.redirect_url(code: Authorization.last.code, state: state)) }
+          specify { expect(Authorization.last).to be_plain }
+          specify { expect(Authorization.last.challenge).to eql(code_verifier) }
         end
 
         context "when the client requested a token using a valid PKCE with the default code_challenge_method" do
@@ -126,10 +131,12 @@ RSpec.describe '/oauth' do
               state: state,
               redirect_uri: client.redirect_uri
             }
-            post "/oauth", params: { code_verifier: code_verifier }
+            post "/oauth"
           end
 
           specify { expect(response).to redirect_to(client.redirect_url(code: Authorization.last.code, state: state)) }
+          specify { expect(Authorization.last).to be_plain }
+          specify { expect(Authorization.last.challenge).to eql(code_verifier) }
         end
 
         context "when the client did not make an appropriate request" do