Getting token from user credential flow
This commit is contained in:
parent
8d8faf684c
commit
a1f3d5f5f9
15 changed files with 143 additions and 93 deletions
22
app/controllers/openid/LICENSE
Normal file
22
app/controllers/openid/LICENSE
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
This code is based on https://github.com/nov/openid_connect_sample
|
||||
|
||||
Copyright (c) 2011 nov matake
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -16,7 +16,7 @@ class DiscoveryController < ApplicationController
|
|||
jrd = {
|
||||
links: [{
|
||||
rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE,
|
||||
href: "http://0.0.0.0:3000"
|
||||
href: root_path
|
||||
}]
|
||||
}
|
||||
jrd[:subject] = params[:resource] if params[:resource].present?
|
||||
|
|
@ -25,12 +25,12 @@ class DiscoveryController < ApplicationController
|
|||
|
||||
def openid_configuration
|
||||
config = OpenIDConnect::Discovery::Provider::Config::Response.new(
|
||||
issuer: "http://0.0.0.0:3000",
|
||||
issuer: root_path,
|
||||
authorization_endpoint: "#{authorizations_url}/new",
|
||||
token_endpoint: access_tokens_url,
|
||||
userinfo_endpoint: user_info_url,
|
||||
jwks_uri: "#{authorizations_url}/jwks.json",
|
||||
registration_endpoint: "http://0.0.0.0:3000/connect",
|
||||
registration_endpoint: "#{root_path}/connect",
|
||||
scopes_supported: "iss",
|
||||
response_types_supported: "Client.available_response_types",
|
||||
grant_types_supported: "Client.available_grant_types",
|
||||
|
|
|
|||
6
app/models/o_auth_application.rb
Normal file
6
app/models/o_auth_application.rb
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
class OAuthApplication < ActiveRecord::Base
|
||||
validates :client_id, presence: true, uniqueness: true
|
||||
validates :client_secret, presence: true
|
||||
|
||||
has_many :tokens
|
||||
end
|
||||
19
app/models/token.rb
Normal file
19
app/models/token.rb
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
class Token < ActiveRecord::Base
|
||||
belongs_to :o_auth_application
|
||||
|
||||
before_validation :setup, on: :create
|
||||
|
||||
validates :token, presence: true, uniqueness: true
|
||||
|
||||
def setup
|
||||
self.token = SecureRandom.hex(32)
|
||||
self.expires_at = 24.hours.from_now
|
||||
end
|
||||
|
||||
def bearer_token
|
||||
@bearer_token ||= Rack::OAuth2::AccessToken::Bearer.new(
|
||||
access_token: token,
|
||||
expires_in: (expires_at - Time.now.utc).to_i
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -76,6 +76,8 @@ class User < ActiveRecord::Base
|
|||
|
||||
has_many :reports
|
||||
|
||||
has_many :o_auth_applications
|
||||
|
||||
before_save :guard_unconfirmed_email,
|
||||
:save_person!
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ module Diaspora
|
|||
# Custom directories with classes and modules you want to be autoloadable.
|
||||
config.autoload_paths += %W{#{config.root}/app}
|
||||
config.autoload_once_paths += %W{#{config.root}/lib}
|
||||
config.autoload_paths += %W{#{config.root}/lib/openid_connect}
|
||||
|
||||
# Only load the plugins named here, in the order given (default is alphabetical).
|
||||
# :all can be used as a placeholder for all plugins not explicitly named.
|
||||
|
|
|
|||
10
db/migrate/20150613202109_create_o_auth_applications.rb
Normal file
10
db/migrate/20150613202109_create_o_auth_applications.rb
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
class CreateOAuthApplications < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :o_auth_applications do |t|
|
||||
t.string :client_id
|
||||
t.string :client_secret
|
||||
|
||||
t.timestamps null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
14
db/migrate/20150614134031_create_tokens.rb
Normal file
14
db/migrate/20150614134031_create_tokens.rb
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
class CreateTokens < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :tokens do |t|
|
||||
t.belongs_to :o_auth_application
|
||||
t.string :token
|
||||
t.datetime :expires_at
|
||||
t.timestamps null: false
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :tokens
|
||||
end
|
||||
end
|
||||
15
db/schema.rb
15
db/schema.rb
|
|
@ -236,6 +236,13 @@ ActiveRecord::Schema.define(version: 20151003142048) do
|
|||
add_index "notifications", ["target_id"], name: "index_notifications_on_target_id", using: :btree
|
||||
add_index "notifications", ["target_type", "target_id"], name: "index_notifications_on_target_type_and_target_id", length: {"target_type"=>190, "target_id"=>nil}, using: :btree
|
||||
|
||||
create_table "o_auth_applications", force: :cascade do |t|
|
||||
t.string "client_id", limit: 255
|
||||
t.string "client_secret", limit: 255
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "o_embed_caches", force: :cascade do |t|
|
||||
t.string "url", limit: 1024, null: false
|
||||
t.text "data", limit: 65535, null: false
|
||||
|
|
@ -528,6 +535,14 @@ ActiveRecord::Schema.define(version: 20151003142048) do
|
|||
|
||||
add_index "tags", ["name"], name: "index_tags_on_name", unique: true, length: {"name"=>191}, using: :btree
|
||||
|
||||
create_table "tokens", force: :cascade do |t|
|
||||
t.integer "o_auth_application_id", limit: 4
|
||||
t.string "token", limit: 255
|
||||
t.datetime "expires_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "user_preferences", force: :cascade do |t|
|
||||
t.string "email_type", limit: 255
|
||||
t.integer "user_id", limit: 4
|
||||
|
|
|
|||
22
lib/openid_connect/LICENSE
Normal file
22
lib/openid_connect/LICENSE
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
This code is based on https://github.com/nov/openid_connect_sample
|
||||
|
||||
Copyright (c) 2011 nov matake
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -3,80 +3,8 @@ class AuthorizationEndpoint
|
|||
delegate :call, to: :app
|
||||
|
||||
def initialize(allow_approval = false, approved = false)
|
||||
@account = nil
|
||||
@app = Rack::OAuth2::Server::Authorize.new do |req, res|
|
||||
@client = nil # Find the client
|
||||
res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@client.redirect_uris)
|
||||
if res.protocol_params_location == :fragment && req.nonce.blank?
|
||||
req.invalid_request! 'nonce required'
|
||||
end
|
||||
@scopes = req.scope.inject([]) do |_scopes_, scope|
|
||||
_scopes_ << Scope.find_by_name(scope) or req.invalid_scope! "Unknown scope: #{scope}"
|
||||
end
|
||||
@request_object = if (@_request_ = req.request).present?
|
||||
OpenIDConnect::RequestObject.decode req.request, nil # @client.secret
|
||||
elsif (@request_uri = req.request_uri).present?
|
||||
OpenIDConnect::RequestObject.fetch req.request_uri, nil # @client.secret
|
||||
end
|
||||
if Client.available_response_types.include? Array(req.response_type).collect(&:to_s).join(' ')
|
||||
if allow_approval
|
||||
if approved
|
||||
approved! req, res
|
||||
else
|
||||
req.access_denied!
|
||||
end
|
||||
else
|
||||
@response_type = req.response_type
|
||||
end
|
||||
else
|
||||
req.unsupported_response_type!
|
||||
end
|
||||
req.unsupported_response_type! # Not supported yet
|
||||
end
|
||||
end
|
||||
|
||||
def approved!(req, res)
|
||||
response_types = Array(req.response_type)
|
||||
if response_types.include? :code
|
||||
authorization = account.authorizations.create!(client: @client, redirect_uri: res.redirect_uri, nonce: req.nonce)
|
||||
authorization.scopes << scopes
|
||||
if @request_object
|
||||
authorization.create_authorization_request_object!(
|
||||
request_object: RequestObject.new(
|
||||
jwt_string: @request_object.to_jwt(@client.secret, :HS256)
|
||||
)
|
||||
)
|
||||
end
|
||||
res.code = authorization.code
|
||||
end
|
||||
if response_types.include? :token
|
||||
access_token = account.access_tokens.create!(client: @client)
|
||||
access_token.scopes << scopes
|
||||
if @request_object
|
||||
access_token.create_access_token_request_object!(
|
||||
request_object: RequestObject.new(
|
||||
jwt_string: @request_object.to_jwt(@client.secret, :HS256)
|
||||
)
|
||||
)
|
||||
end
|
||||
res.access_token = access_token.to_bearer_token
|
||||
end
|
||||
if response_types.include? :id_token
|
||||
_id_token_ = account.id_tokens.create!(
|
||||
client: @client,
|
||||
nonce: req.nonce
|
||||
)
|
||||
if @request_object
|
||||
_id_token_.create_id_token_request_object!(
|
||||
request_object: RequestObject.new(
|
||||
jwt_string: @request_object.to_jwt(@client.secret, :HS256)
|
||||
)
|
||||
)
|
||||
end
|
||||
res.id_token = _id_token_.to_jwt(
|
||||
code: (res.respond_to?(:code) ? res.code : nil),
|
||||
access_token: (res.respond_to?(:access_token) ? res.access_token : nil)
|
||||
)
|
||||
end
|
||||
res.approve!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,26 +4,19 @@ class TokenEndpoint
|
|||
|
||||
def initialize
|
||||
@app = Rack::OAuth2::Server::Token.new do |req, res|
|
||||
client = Client.find_by_identifier(req.client_id) || req.invalid_client!
|
||||
client.secret == req.client_secret || req.invalid_client!
|
||||
case req.grant_type
|
||||
when :client_credentials
|
||||
res.access_token = client.access_tokens.create!.to_bearer_token
|
||||
when :authorization_code
|
||||
authorization = client.authorizations.valid.find_by_code(req.code)
|
||||
req.invalid_grant! if authorization.blank? || !authorization.valid_redirect_uri?(req.redirect_uri)
|
||||
access_token = authorization.access_token
|
||||
res.access_token = access_token.to_bearer_token
|
||||
if access_token.accessible?(Scope::OPENID)
|
||||
res.id_token = access_token.account.id_tokens.create!(
|
||||
client: access_token.client,
|
||||
nonce: authorization.nonce,
|
||||
request_object: authorization.request_object
|
||||
).to_response_object.to_jwt IdToken.config[:private_key]
|
||||
when :password
|
||||
# If the grant type is password, the application does not have to be known
|
||||
# If it does not exist, insert into DB
|
||||
user = User.find_for_database_authentication(username: req.username)
|
||||
o_auth_app = OAuthApplication.find_by_client_id req.client_id
|
||||
o_auth_app ||= OAuthApplication.create!(client_id: req.client_id, client_secret: req.client_secret)
|
||||
if user.valid_password? req.password
|
||||
res.access_token = o_auth_app.tokens.create!.bearer_token
|
||||
end
|
||||
else
|
||||
req.unsupported_grant_type!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
8
spec/lib/openid_connect/token_endpoint_spec.rb
Normal file
8
spec/lib/openid_connect/token_endpoint_spec.rb
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
require 'rspec'
|
||||
|
||||
describe TokenEndpoint do
|
||||
|
||||
it "shoud generate a token" do
|
||||
|
||||
end
|
||||
end
|
||||
5
spec/models/o_auth_application_spec.rb
Normal file
5
spec/models/o_auth_application_spec.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe OAuthApplication, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
5
spec/models/token_spec.rb
Normal file
5
spec/models/token_spec.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Token, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
Loading…
Reference in a new issue