Add pairwise pseudonymous identifier support

Squashed commits:

[a182de7] Fix pronto/travis errors
This commit is contained in:
theworldbright 2015-08-01 17:09:23 +09:00
parent d834a1d4d0
commit 99d6d7b3e7
17 changed files with 137 additions and 50 deletions

View file

@ -18,7 +18,7 @@ module Api
registration_endpoint: api_openid_connect_clients_url, registration_endpoint: api_openid_connect_clients_url,
authorization_endpoint: new_api_openid_connect_authorization_url, authorization_endpoint: new_api_openid_connect_authorization_url,
token_endpoint: api_openid_connect_access_tokens_url, token_endpoint: api_openid_connect_access_tokens_url,
userinfo_endpoint: api_v0_user_url, userinfo_endpoint: api_openid_connect_user_info_url,
jwks_uri: File.join(root_url, "api", "openid_connect", "jwks.json"), jwks_uri: File.join(root_url, "api", "openid_connect", "jwks.json"),
scopes_supported: Api::OpenidConnect::Scope.pluck(:name), scopes_supported: Api::OpenidConnect::Scope.pluck(:name),
response_types_supported: Api::OpenidConnect::OAuthApplication.available_response_types, response_types_supported: Api::OpenidConnect::OAuthApplication.available_response_types,

View file

@ -8,7 +8,7 @@ module Api
end end
def show def show
render json: current_user, serializer: UserInfoSerializer render json: current_user, serializer: UserInfoSerializer, authorization: current_token.authorization
end end
def current_user def current_user

View file

@ -23,19 +23,29 @@ module Api
end end
def claims def claims
sub = build_sub
@claims ||= { @claims ||= {
iss: AppConfig.environment.url, iss: AppConfig.environment.url,
# TODO: Convert to proper PPID sub: sub,
sub: "#{AppConfig.environment.url}#{authorization.o_auth_application.client_id}#{authorization.user.id}",
aud: authorization.o_auth_application.client_id, aud: authorization.o_auth_application.client_id,
exp: expires_at.to_i, exp: expires_at.to_i,
iat: created_at.to_i, iat: created_at.to_i,
auth_time: authorization.user.current_sign_in_at.to_i, auth_time: authorization.user.current_sign_in_at.to_i,
nonce: nonce, nonce: nonce
acr: 0 # TODO: Adjust ?
} }
end end
def build_sub
if authorization.o_auth_application.ppid?
sector_identifier = authorization.o_auth_application.sector_identifier_uri
pairwise_pseudonymous_identifier =
authorization.user.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: sector_identifier)
pairwise_pseudonymous_identifier.guid
else
authorization.user.diaspora_handle
end
end
# TODO: Add support for request objects # TODO: Add support for request objects
end end
end end

View file

@ -8,24 +8,15 @@ module Api
validates :client_secret, presence: true validates :client_secret, presence: true
validates :client_name, presence: true validates :client_name, presence: true
serialize :redirect_uris, JSON %i(redirect_uris response_types grant_types contacts).each do |serializable|
serialize :response_types, JSON serialize serializable, JSON
serialize :grant_types, JSON end
serialize :contacts, JSON
before_validation :setup, on: :create before_validation :setup, on: :create
def setup def setup
self.client_id = SecureRandom.hex(16) self.client_id = SecureRandom.hex(16)
self.client_secret = SecureRandom.hex(32) self.client_secret = SecureRandom.hex(32)
self.response_types = []
self.grant_types = []
self.application_type = "web"
self.contacts = []
self.logo_uri = ""
self.client_uri = ""
self.policy_uri = ""
self.tos_uri = ""
end end
class << self class << self
@ -46,12 +37,19 @@ module Api
def supported_metadata def supported_metadata
%i(client_name response_types grant_types application_type %i(client_name response_types grant_types application_type
contacts logo_uri client_uri policy_uri tos_uri redirect_uris) contacts logo_uri client_uri policy_uri tos_uri redirect_uris
sector_identifier_uri subject_type)
end end
def registrar_attributes(registrar) def registrar_attributes(registrar)
supported_metadata.each_with_object({}) do |key, attr| supported_metadata.each_with_object({}) do |key, attr|
attr[key] = registrar.public_send(key) if registrar.public_send(key) value = registrar.public_send(key)
next unless value
if key == :subject_type
attr[:ppid] = (value == "pairwise")
else
attr[key] = value
end
end end
end end
end end

View file

@ -0,0 +1,22 @@
module Api
module OpenidConnect
class PairwisePseudonymousIdentifier < ActiveRecord::Base
self.table_name = "ppid"
belongs_to :o_auth_application
belongs_to :user
validates :user, presence: true
validates :sector_identifier, presence: true, uniqueness: {scope: :user}
validates :guid, presence: true, uniqueness: true
before_validation :setup, on: :create
private
def setup
self.guid = SecureRandom.hex(16)
end
end
end
end

View file

@ -76,6 +76,7 @@ class User < ActiveRecord::Base
has_many :reports has_many :reports
has_many :pairwise_pseudonymous_identifiers, class_name: "Api::OpenidConnect::PairwisePseudonymousIdentifier"
has_many :authorizations, class_name: "Api::OpenidConnect::Authorization" has_many :authorizations, class_name: "Api::OpenidConnect::Authorization"
has_many :o_auth_applications, through: :authorizations, class_name: "Api::OpenidConnect::OAuthApplication" has_many :o_auth_applications, through: :authorizations, class_name: "Api::OpenidConnect::OAuthApplication"

View file

@ -2,7 +2,15 @@ class UserInfoSerializer < ActiveModel::Serializer
attributes :sub, :nickname, :profile, :picture, :zoneinfo attributes :sub, :nickname, :profile, :picture, :zoneinfo
def sub def sub
object.diaspora_handle # TODO: Change to proper sub auth = serialization_options[:authorization]
if auth.o_auth_application.ppid?
sector_identifier = auth.o_auth_application.sector_identifier_uri
pairwise_pseudonymous_identifier =
object.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: sector_identifier)
pairwise_pseudonymous_identifier.guid
else
object.diaspora_handle
end
end end
def nickname def nickname

View file

@ -5,15 +5,18 @@ class CreateOAuthApplications < ActiveRecord::Migration
t.string :client_id t.string :client_id
t.string :client_secret t.string :client_secret
t.string :client_name t.string :client_name
t.string :redirect_uris t.string :redirect_uris
t.string :response_types t.string :response_types
t.string :grant_types t.string :grant_types
t.string :application_type t.string :application_type, default: "web"
t.string :contacts t.string :contacts
t.string :logo_uri t.string :logo_uri
t.string :client_uri t.string :client_uri
t.string :policy_uri t.string :policy_uri
t.string :tos_uri t.string :tos_uri
t.string :sector_identifier_uri
t.boolean :ppid, default: false
t.timestamps null: false t.timestamps null: false
end end

View file

@ -1,7 +1,7 @@
class CreateScope < ActiveRecord::Migration class CreateScope < ActiveRecord::Migration
def change def change
create_table :scopes do |t| create_table :scopes do |t|
t.string :name t.primary_key :name, :string
t.timestamps null: false t.timestamps null: false
end end

View file

@ -0,0 +1,11 @@
class CreatePairwisePseudonymousIdentifiers < ActiveRecord::Migration
def change
create_table :ppid do |t|
t.belongs_to :o_auth_application, index: true
t.belongs_to :user, index: true
t.primary_key :guid, :string, limit: 32
t.string :sector_identifier
end
end
end

View file

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150724152052) do ActiveRecord::Schema.define(version: 20150801074555) do
create_table "account_deletions", force: :cascade do |t| create_table "account_deletions", force: :cascade do |t|
t.string "diaspora_handle", limit: 255 t.string "diaspora_handle", limit: 255
@ -278,21 +278,23 @@ ActiveRecord::Schema.define(version: 20150724152052) do
add_index "o_auth_access_tokens", ["authorization_id"], name: "index_o_auth_access_tokens_on_authorization_id", using: :btree add_index "o_auth_access_tokens", ["authorization_id"], name: "index_o_auth_access_tokens_on_authorization_id", using: :btree
create_table "o_auth_applications", force: :cascade do |t| create_table "o_auth_applications", force: :cascade do |t|
t.integer "user_id", limit: 4 t.integer "user_id", limit: 4
t.string "client_id", limit: 255 t.string "client_id", limit: 255
t.string "client_secret", limit: 255 t.string "client_secret", limit: 255
t.string "client_name", limit: 255 t.string "client_name", limit: 255
t.string "redirect_uris", limit: 255 t.string "redirect_uris", limit: 255
t.string "response_types", limit: 255 t.string "response_types", limit: 255
t.string "grant_types", limit: 255 t.string "grant_types", limit: 255
t.string "application_type", limit: 255 t.string "application_type", limit: 255, default: "web"
t.string "contacts", limit: 255 t.string "contacts", limit: 255
t.string "logo_uri", limit: 255 t.string "logo_uri", limit: 255
t.string "client_uri", limit: 255 t.string "client_uri", limit: 255
t.string "policy_uri", limit: 255 t.string "policy_uri", limit: 255
t.string "tos_uri", limit: 255 t.string "tos_uri", limit: 255
t.datetime "created_at", null: false t.string "sector_identifier_uri", limit: 255
t.datetime "updated_at", null: false t.boolean "ppid", default: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end end
add_index "o_auth_applications", ["user_id"], name: "index_o_auth_applications_on_user_id", using: :btree add_index "o_auth_applications", ["user_id"], name: "index_o_auth_applications_on_user_id", using: :btree
@ -463,6 +465,16 @@ ActiveRecord::Schema.define(version: 20150724152052) do
add_index "posts", ["tweet_id"], name: "index_posts_on_tweet_id", length: {"tweet_id"=>191}, using: :btree add_index "posts", ["tweet_id"], name: "index_posts_on_tweet_id", length: {"tweet_id"=>191}, using: :btree
add_index "posts", ["type", "pending", "id"], name: "index_posts_on_type_and_pending_and_id", using: :btree add_index "posts", ["type", "pending", "id"], name: "index_posts_on_type_and_pending_and_id", using: :btree
create_table "ppid", force: :cascade do |t|
t.integer "o_auth_application_id", limit: 4
t.integer "user_id", limit: 4
t.string "guid", limit: 32
t.string "sector_identifier", limit: 255
end
add_index "ppid", ["o_auth_application_id"], name: "index_ppid_on_o_auth_application_id", using: :btree
add_index "ppid", ["user_id"], name: "index_ppid_on_user_id", using: :btree
create_table "profiles", force: :cascade do |t| create_table "profiles", force: :cascade do |t|
t.string "diaspora_handle", limit: 255 t.string "diaspora_handle", limit: 255
t.string "first_name", limit: 127 t.string "first_name", limit: 127

View file

@ -28,5 +28,9 @@ end
When /^I parse the tokens and use it obtain user info$/ do When /^I parse the tokens and use it obtain user info$/ do
client_json = JSON.parse(last_response.body) client_json = JSON.parse(last_response.body)
access_token = client_json["access_token"] access_token = client_json["access_token"]
encoded_id_token = client_json["id_token"]
decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token,
Api::OpenidConnect::IdTokenConfig.public_key
expect(decoded_token.sub).to eq(@me.diaspora_handle)
get api_openid_connect_user_info_path, access_token: access_token get api_openid_connect_user_info_path, access_token: access_token
end end

View file

@ -47,7 +47,7 @@ class AccountDeleter
#user deletions #user deletions
def normal_ar_user_associates_to_delete def normal_ar_user_associates_to_delete
%i(tag_followings invitations_to_me services aspects user_preferences %i(tag_followings invitations_to_me services aspects user_preferences
notifications blocks authorizations o_auth_applications) notifications blocks authorizations o_auth_applications pairwise_pseudonymous_identifiers)
end end
def special_ar_user_associations def special_ar_user_associations

View file

@ -6,17 +6,19 @@ describe Api::OpenidConnect::ClientsController, type: :controller do
it "should return a client id" do it "should return a client id" do
post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client",
response_types: [], grant_types: [], application_type: "web", contacts: [], response_types: [], grant_types: [], application_type: "web", contacts: [],
logo_uri: "http://test.com/logo.png", client_uri: "http://test.com/client", logo_uri: "http://example.com/logo.png", client_uri: "http://example.com/client",
policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos" policy_uri: "http://example.com/policy", tos_uri: "http://example.com/tos",
sector_identifier_uri: "http://example.com/uris", subject_type: "pairwise"
client_json = JSON.parse(response.body) client_json = JSON.parse(response.body)
expect(client_json["o_auth_application"]["client_id"].length).to eq(32) expect(client_json["o_auth_application"]["client_id"].length).to eq(32)
expect(client_json["o_auth_application"]["ppid"]).to eq(true)
end end
end end
context "when redirect uri is missing" do context "when redirect uri is missing" do
it "should return a invalid_client_metadata error" do it "should return a invalid_client_metadata error" do
post :create, response_types: [], grant_types: [], application_type: "web", contacts: [], post :create, response_types: [], grant_types: [], application_type: "web", contacts: [],
logo_uri: "http://test.com/logo.png", client_uri: "http://test.com/client", logo_uri: "http://example.com/logo.png", client_uri: "http://example.com/client",
policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos" policy_uri: "http://example.com/policy", tos_uri: "http://example.com/tos"
client_json = JSON.parse(response.body) client_json = JSON.parse(response.body)
expect(client_json["error"]).to have_content("invalid_client_metadata") expect(client_json["error"]).to have_content("invalid_client_metadata")
end end
@ -24,8 +26,9 @@ describe Api::OpenidConnect::ClientsController, type: :controller do
context "when redirect client_name is missing" do context "when redirect client_name is missing" do
it "should return a invalid_client_metadata error" do it "should return a invalid_client_metadata error" do
post :create, redirect_uris: ["http://localhost"], response_types: [], grant_types: [], post :create, redirect_uris: ["http://localhost"], response_types: [], grant_types: [],
application_type: "web", contacts: [], logo_uri: "http://test.com/logo.png", application_type: "web", contacts: [], logo_uri: "http://example.com/logo.png",
client_uri: "http://test.com/client", policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos" client_uri: "http://example.com/client", policy_uri: "http://example.com/policy",
tos_uri: "http://example.com/tos"
client_json = JSON.parse(response.body) client_json = JSON.parse(response.body)
expect(client_json["error"]).to have_content("invalid_client_metadata") expect(client_json["error"]).to have_content("invalid_client_metadata")
end end

View file

@ -18,10 +18,17 @@ describe Api::OpenidConnect::DiscoveryController, type: :controller do
end end
describe "#configuration" do describe "#configuration" do
it "should have the issuer as the root url" do before do
get :configuration get :configuration
end
it "should have the issuer as the root url" do
json_body = JSON.parse(response.body) json_body = JSON.parse(response.body)
expect(json_body["issuer"]).to eq("http://test.host/") expect(json_body["issuer"]).to eq("http://test.host/")
end end
it "should have the appropriate user info endpoint" do
json_body = JSON.parse(response.body)
expect(json_body["userinfo_endpoint"]).to eq(api_openid_connect_user_info_url)
end
end end
end end

View file

@ -1,10 +1,12 @@
require "spec_helper" require "spec_helper"
describe Api::V0::UsersController do describe Api::OpenidConnect::UserInfoController do
# TODO: Replace with factory # TODO: Replace with factory
let!(:client) do let!(:client) do
Api::OpenidConnect::OAuthApplication.create!( Api::OpenidConnect::OAuthApplication.create!(
client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) client_name: "Diaspora Test Client",
redirect_uris: ["http://localhost:3000/"],
ppid: true, sector_identifier_uri: "https://example.com/uri")
end end
let(:auth_with_read) do let(:auth_with_read) do
auth = Api::OpenidConnect::Authorization.create!(o_auth_application: client, user: alice) auth = Api::OpenidConnect::Authorization.create!(o_auth_application: client, user: alice)
@ -21,6 +23,9 @@ describe Api::V0::UsersController do
it "shows the info" do it "shows the info" do
json_body = JSON.parse(response.body) json_body = JSON.parse(response.body)
expected_sub =
alice.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: "https://example.com/uri").guid
expect(json_body["sub"]).to eq(expected_sub)
expect(json_body["nickname"]).to eq(alice.name) expect(json_body["nickname"]).to eq(alice.name)
expect(json_body["profile"]).to eq(File.join(AppConfig.environment.url, "people", alice.guid).to_s) expect(json_body["profile"]).to eq(File.join(AppConfig.environment.url, "people", alice.guid).to_s)
end end

View file

@ -3,7 +3,8 @@ require "spec_helper"
describe Api::OpenidConnect::TokenEndpoint, type: :request do describe Api::OpenidConnect::TokenEndpoint, type: :request do
let!(:client) do let!(:client) do
Api::OpenidConnect::OAuthApplication.create!( Api::OpenidConnect::OAuthApplication.create!(
redirect_uris: ["http://localhost:3000/"], client_name: "diaspora client") redirect_uris: ["http://localhost:3000/"], client_name: "diaspora client",
ppid: true, sector_identifier_uri: "https://example.com/uri")
end end
let!(:auth) { let!(:auth) {
Api::OpenidConnect::Authorization.find_or_create_by( Api::OpenidConnect::Authorization.find_or_create_by(
@ -28,6 +29,8 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do
encoded_id_token = json["id_token"] encoded_id_token = json["id_token"]
decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token,
Api::OpenidConnect::IdTokenConfig.public_key Api::OpenidConnect::IdTokenConfig.public_key
expected_guid = bob.pairwise_pseudonymous_identifiers.find_by(sector_identifier: "https://example.com/uri").guid
expect(decoded_token.sub).to eq(expected_guid)
expect(decoded_token.exp).to be > Time.zone.now.utc.to_i expect(decoded_token.exp).to be > Time.zone.now.utc.to_i
end end