Allow current user to be obtained from access token
This commit is contained in:
parent
68d96a3189
commit
beae77102d
18 changed files with 116 additions and 79 deletions
|
|
@ -1,6 +1,5 @@
|
|||
class Api::V2::BaseController < ApplicationController
|
||||
include Openid::Authentication
|
||||
include OpenidConnect::Authentication
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_filter :require_access_token
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
class Api::V2::UsersController < Api::V2::BaseController
|
||||
|
||||
def show
|
||||
render json: current_user
|
||||
render json: user
|
||||
end
|
||||
|
||||
private
|
||||
def user
|
||||
current_token.o_auth_application.user
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
class OAuthApplication < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
|
||||
validates :user_id, presence: true
|
||||
validates :client_id, presence: true, uniqueness: true
|
||||
validates :client_secret, presence: true
|
||||
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ Diaspora::Application.routes.draw do
|
|||
resources :authorizations, only: [:new, :create]
|
||||
match 'connect', to: 'connect#show', via: [:get, :post]
|
||||
match '.well-known/:id', to: 'discovery#show' , via: [:get, :post]
|
||||
post 'access_tokens', to: proc { |env| Openid::TokenEndpoint.new.call(env) }
|
||||
post 'access_tokens', to: proc { |env| OpenidConnect::TokenEndpoint.new.call(env) }
|
||||
end
|
||||
|
||||
api_version(:module => "Api::V2", path: {value: "api/v2"}, default: true) do
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
class CreateOAuthApplications < ActiveRecord::Migration
|
||||
def change
|
||||
def self.up
|
||||
create_table :o_auth_applications do |t|
|
||||
t.belongs_to :user, index: true
|
||||
t.string :client_id
|
||||
t.string :client_secret
|
||||
|
||||
t.timestamps null: false
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :o_auth_applications
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
class CreateTokens < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :tokens do |t|
|
||||
t.belongs_to :o_auth_application
|
||||
t.belongs_to :o_auth_application, index: true
|
||||
t.string :token
|
||||
t.datetime :expires_at
|
||||
t.timestamps null: false
|
||||
|
|
|
|||
|
|
@ -237,12 +237,15 @@ ActiveRecord::Schema.define(version: 20151003142048) do
|
|||
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.integer "user_id", limit: 4
|
||||
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
|
||||
|
||||
add_index "o_auth_applications", ["user_id"], name: "index_o_auth_applications_on_user_id", using: :btree
|
||||
|
||||
create_table "o_embed_caches", force: :cascade do |t|
|
||||
t.string "url", limit: 1024, null: false
|
||||
t.text "data", limit: 65535, null: false
|
||||
|
|
@ -543,6 +546,8 @@ ActiveRecord::Schema.define(version: 20151003142048) do
|
|||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
add_index "tokens", ["o_auth_application_id"], name: "index_tokens_on_o_auth_application_id", using: :btree
|
||||
|
||||
create_table "user_preferences", force: :cascade do |t|
|
||||
t.string "email_type", limit: 255
|
||||
t.integer "user_id", limit: 4
|
||||
|
|
|
|||
19
features/desktop/oauth_password_flow.feature
Normal file
19
features/desktop/oauth_password_flow.feature
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
@javascript
|
||||
Feature: Access protected resources using password flow
|
||||
# TODO: Add tests for expired access tokens
|
||||
Background:
|
||||
Given a user with username "kent"
|
||||
|
||||
Scenario: Valid bearer tokens sent via Authorization Request Header Field
|
||||
|
||||
Scenario: Valid bearer tokens sent via Form Encoded Parameter
|
||||
|
||||
Scenario: Valid bearer tokens sent via URI query parameter
|
||||
When I send a post request to the token endpoint using "kent"'s credentials
|
||||
And I use received valid bearer tokens to access user info via URI query parameter
|
||||
Then I should receive "kent"'s id, username, and email
|
||||
|
||||
Scenario: Invalid bearer tokens sent via URI query parameter
|
||||
When I send a post request to the token endpoint using "kent"'s credentials
|
||||
And I use invalid bearer tokens to access user info via URI query parameter
|
||||
Then I should receive an "invalid_token" error
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
@javascript
|
||||
# TODO: Add tests for expired access tokens
|
||||
# TODO: Add tests to check for WWW-Authenticate response header field as according to RFC 6750
|
||||
Feature: Access protected resources using bearer access token
|
||||
Background:
|
||||
Given a user with username "bob"
|
||||
And I log in manually as "bob" with password "password"
|
||||
And I send a post request to the token endpoint using "bob"'s credentials
|
||||
|
||||
Scenario: Valid bearer tokens sent via Authorization Request Header Field
|
||||
# TODO: Add tests
|
||||
|
||||
Scenario: Valid bearer tokens sent via Form Encoded Parameter
|
||||
# TODO: Add tests
|
||||
|
||||
Scenario: Valid bearer tokens sent via URI query parameter
|
||||
When I use received valid bearer tokens to access user info via URI query parameter
|
||||
Then I should receive "bob"'s id, username, and email
|
||||
# TODO: I want to confirm that the cache-control header in the response is private as according to RFC 6750
|
||||
# Unfortunately, selenium doesn't allow access to response headers
|
||||
|
||||
Scenario: Invalid bearer tokens sent via URI query parameter
|
||||
When I use invalid bearer tokens to access user info via URI query parameter
|
||||
Then I should receive an "invalid_token" error
|
||||
|
||||
Scenario: Valid bearer tokens sent via URI query parameter but user is logged out
|
||||
When I log out manually
|
||||
And I use received valid bearer tokens to access user info via URI query parameter
|
||||
Then I should see "Sign in" in the content
|
||||
When I log in manually as "bob" with password "password"
|
||||
Then I should receive "bob"'s id, username, and email
|
||||
|
|
@ -10,13 +10,13 @@ end
|
|||
|
||||
When /^I use received valid bearer tokens to access user info via URI query parameter$/ do
|
||||
accessTokenJson = JSON.parse(last_response.body)
|
||||
userInfoEndPointURL = "/openid/user_info/"
|
||||
userInfoEndPointURL = "/api/v2/user/"
|
||||
userInfoEndPointURLQuery = "?access_token=" + accessTokenJson["access_token"]
|
||||
visit userInfoEndPointURL + userInfoEndPointURLQuery
|
||||
end
|
||||
|
||||
When /^I use invalid bearer tokens to access user info via URI query parameter$/ do
|
||||
userInfoEndPointURL = "/openid/user_info/"
|
||||
userInfoEndPointURL = "/api/v2/user/"
|
||||
userInfoEndPointURLQuery = "?access_token=" + SecureRandom.hex(32)
|
||||
visit userInfoEndPointURL + userInfoEndPointURLQuery
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
module Openid
|
||||
class TokenEndpoint
|
||||
attr_accessor :app
|
||||
delegate :call, to: :app
|
||||
|
||||
def initialize
|
||||
@app = Rack::OAuth2::Server::Token.new do |req, res|
|
||||
case req.grant_type
|
||||
when :password
|
||||
o_auth_app = retrieveOrCreateNewClientApplication(req)
|
||||
user = User.find_for_database_authentication(username: req.username)
|
||||
if o_auth_app && user && user.valid_password?(req.password)
|
||||
res.access_token = o_auth_app.tokens.create!.bearer_token
|
||||
else
|
||||
req.invalid_grant!
|
||||
end
|
||||
else
|
||||
res.unsupported_grant_type!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def retrieveOrCreateNewClientApplication(req)
|
||||
retrieveClient(req) || createClient(req)
|
||||
end
|
||||
|
||||
def retrieveClient(req)
|
||||
OAuthApplication.find_by_client_id req.client_id
|
||||
end
|
||||
|
||||
def createClient(req)
|
||||
OAuthApplication.create!(client_id: req.client_id, client_secret: req.client_secret)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
module Openid
|
||||
module OpenidConnect
|
||||
module Authentication
|
||||
|
||||
def self.included(klass)
|
||||
|
|
@ -22,9 +22,9 @@ module Openid
|
|||
end
|
||||
end
|
||||
|
||||
# Scopes should be implemented here
|
||||
# TODO: Scopes should be implemented here
|
||||
def required_scopes
|
||||
nil # as default
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
module Openid
|
||||
module OpenidConnect
|
||||
class AuthorizationEndpoint
|
||||
attr_accessor :app, :account, :client, :redirect_uri, :response_type, :scopes, :_request_, :request_uri, :request_object
|
||||
delegate :call, to: :app
|
||||
39
lib/openid_connect/token_endpoint.rb
Normal file
39
lib/openid_connect/token_endpoint.rb
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
module OpenidConnect
|
||||
class TokenEndpoint
|
||||
attr_accessor :app
|
||||
delegate :call, to: :app
|
||||
|
||||
def initialize
|
||||
@app = Rack::OAuth2::Server::Token.new do |req, res|
|
||||
case req.grant_type
|
||||
when :password
|
||||
user = User.find_for_database_authentication(username: req.username)
|
||||
if user
|
||||
o_auth_app = retrieveOrCreateNewClientApplication(req, user)
|
||||
if o_auth_app && user.valid_password?(req.password)
|
||||
res.access_token = o_auth_app.tokens.create!.bearer_token
|
||||
else
|
||||
req.invalid_grant!
|
||||
end
|
||||
else
|
||||
req.invalid_grant! # TODO: Change to user login
|
||||
end
|
||||
else
|
||||
res.unsupported_grant_type!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def retrieveOrCreateNewClientApplication(req, user)
|
||||
retrieveClient(req, user) || createClient(req, user)
|
||||
end
|
||||
|
||||
def retrieveClient(req, user)
|
||||
user.o_auth_applications.find_by_client_id req.client_id
|
||||
end
|
||||
|
||||
def createClient(req, user)
|
||||
user.o_auth_applications.create!(client_id: req.client_id, client_secret: req.client_secret)
|
||||
end
|
||||
end
|
||||
end
|
||||
19
spec/controllers/api/v2/users_controller_spec.rb
Normal file
19
spec/controllers/api/v2/users_controller_spec.rb
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
require 'spec_helper'
|
||||
|
||||
# TODO: Confirm that the cache-control header in the response is private as according to RFC 6750
|
||||
# TODO: Check for WWW-Authenticate response header field as according to RFC 6750
|
||||
describe Api::V2::UsersController, type: :request do
|
||||
describe "#show" do
|
||||
let!(:application) { bob.o_auth_applications.create!(client_id: 1, client_secret: "secret") }
|
||||
let!(:token) { application.tokens.create!.bearer_token.to_s }
|
||||
|
||||
context "when valid" do
|
||||
it "shows the user's info" do
|
||||
get "/api/v2/user/?access_token=" + token
|
||||
jsonBody = JSON.parse(response.body)
|
||||
expect(jsonBody["username"]).to eq(bob.username)
|
||||
expect(jsonBody["email"]).to eq(bob.email)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe "Token Endpoint", type: :request do
|
||||
describe OpenidConnect::TokenEndpoint, type: :request do
|
||||
describe "password grant type" do
|
||||
context "when the username field is missing" do
|
||||
it "should return an invalid request error" do
|
||||
|
|
@ -59,6 +59,14 @@ def photo_fixture_name
|
|||
@photo_fixture_name = File.join(File.dirname(__FILE__), "fixtures", "button.png")
|
||||
end
|
||||
|
||||
def retrieveAccessToken(user)
|
||||
o_auth_app = OAuthApplication.create!(client_id: 4, client_secret: "azerty")
|
||||
user = User.find_for_database_authentication(username: user.username)
|
||||
if o_auth_app && user && user.valid_password?("bluepin7") # Hard coded password for bob
|
||||
o_auth_app.tokens.create!.bearer_token.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Force fixture rebuild
|
||||
FileUtils.rm_f(Rails.root.join("tmp", "fixture_builder.yml"))
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue