Getting token from user credential flow

This commit is contained in:
Augier 2015-06-14 16:13:16 +02:00 committed by theworldbright
parent 8d8faf684c
commit a1f3d5f5f9
15 changed files with 143 additions and 93 deletions

View 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.

View file

@ -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",

View 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
View 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

View file

@ -76,6 +76,8 @@ class User < ActiveRecord::Base
has_many :reports
has_many :o_auth_applications
before_save :guard_unconfirmed_email,
:save_person!

View file

@ -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.

View 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

View 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

View file

@ -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

View 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.

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,8 @@
require 'rspec'
describe TokenEndpoint do
it "shoud generate a token" do
end
end

View file

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe OAuthApplication, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Token, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end