Allow current user to be obtained from access token

This commit is contained in:
theworldbright 2015-07-06 20:31:52 +09:00 committed by theworldbright
parent 68d96a3189
commit beae77102d
18 changed files with 116 additions and 79 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View 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

View file

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

View file

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