Add pairwise pseudonymous identifier support
Squashed commits: [a182de7] Fix pronto/travis errors
This commit is contained in:
parent
d834a1d4d0
commit
99d6d7b3e7
17 changed files with 137 additions and 50 deletions
|
|
@ -18,7 +18,7 @@ module Api
|
|||
registration_endpoint: api_openid_connect_clients_url,
|
||||
authorization_endpoint: new_api_openid_connect_authorization_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"),
|
||||
scopes_supported: Api::OpenidConnect::Scope.pluck(:name),
|
||||
response_types_supported: Api::OpenidConnect::OAuthApplication.available_response_types,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module Api
|
|||
end
|
||||
|
||||
def show
|
||||
render json: current_user, serializer: UserInfoSerializer
|
||||
render json: current_user, serializer: UserInfoSerializer, authorization: current_token.authorization
|
||||
end
|
||||
|
||||
def current_user
|
||||
|
|
|
|||
|
|
@ -23,19 +23,29 @@ module Api
|
|||
end
|
||||
|
||||
def claims
|
||||
sub = build_sub
|
||||
@claims ||= {
|
||||
iss: AppConfig.environment.url,
|
||||
# TODO: Convert to proper PPID
|
||||
sub: "#{AppConfig.environment.url}#{authorization.o_auth_application.client_id}#{authorization.user.id}",
|
||||
sub: sub,
|
||||
aud: authorization.o_auth_application.client_id,
|
||||
exp: expires_at.to_i,
|
||||
iat: created_at.to_i,
|
||||
auth_time: authorization.user.current_sign_in_at.to_i,
|
||||
nonce: nonce,
|
||||
acr: 0 # TODO: Adjust ?
|
||||
nonce: nonce
|
||||
}
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,24 +8,15 @@ module Api
|
|||
validates :client_secret, presence: true
|
||||
validates :client_name, presence: true
|
||||
|
||||
serialize :redirect_uris, JSON
|
||||
serialize :response_types, JSON
|
||||
serialize :grant_types, JSON
|
||||
serialize :contacts, JSON
|
||||
%i(redirect_uris response_types grant_types contacts).each do |serializable|
|
||||
serialize serializable, JSON
|
||||
end
|
||||
|
||||
before_validation :setup, on: :create
|
||||
|
||||
def setup
|
||||
self.client_id = SecureRandom.hex(16)
|
||||
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
|
||||
|
||||
class << self
|
||||
|
|
@ -46,12 +37,19 @@ module Api
|
|||
|
||||
def supported_metadata
|
||||
%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
|
||||
|
||||
def registrar_attributes(registrar)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -76,6 +76,7 @@ class User < ActiveRecord::Base
|
|||
|
||||
has_many :reports
|
||||
|
||||
has_many :pairwise_pseudonymous_identifiers, class_name: "Api::OpenidConnect::PairwisePseudonymousIdentifier"
|
||||
has_many :authorizations, class_name: "Api::OpenidConnect::Authorization"
|
||||
has_many :o_auth_applications, through: :authorizations, class_name: "Api::OpenidConnect::OAuthApplication"
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,15 @@ class UserInfoSerializer < ActiveModel::Serializer
|
|||
attributes :sub, :nickname, :profile, :picture, :zoneinfo
|
||||
|
||||
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
|
||||
|
||||
def nickname
|
||||
|
|
|
|||
|
|
@ -5,15 +5,18 @@ class CreateOAuthApplications < ActiveRecord::Migration
|
|||
t.string :client_id
|
||||
t.string :client_secret
|
||||
t.string :client_name
|
||||
|
||||
t.string :redirect_uris
|
||||
t.string :response_types
|
||||
t.string :grant_types
|
||||
t.string :application_type
|
||||
t.string :application_type, default: "web"
|
||||
t.string :contacts
|
||||
t.string :logo_uri
|
||||
t.string :client_uri
|
||||
t.string :policy_uri
|
||||
t.string :tos_uri
|
||||
t.string :sector_identifier_uri
|
||||
t.boolean :ppid, default: false
|
||||
|
||||
t.timestamps null: false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
class CreateScope < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :scopes do |t|
|
||||
t.string :name
|
||||
t.primary_key :name, :string
|
||||
|
||||
t.timestamps null: false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
16
db/schema.rb
16
db/schema.rb
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# 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|
|
||||
t.string "diaspora_handle", limit: 255
|
||||
|
|
@ -285,12 +285,14 @@ ActiveRecord::Schema.define(version: 20150724152052) do
|
|||
t.string "redirect_uris", limit: 255
|
||||
t.string "response_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 "logo_uri", limit: 255
|
||||
t.string "client_uri", limit: 255
|
||||
t.string "policy_uri", limit: 255
|
||||
t.string "tos_uri", limit: 255
|
||||
t.string "sector_identifier_uri", limit: 255
|
||||
t.boolean "ppid", default: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
|
@ -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", ["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|
|
||||
t.string "diaspora_handle", limit: 255
|
||||
t.string "first_name", limit: 127
|
||||
|
|
|
|||
|
|
@ -28,5 +28,9 @@ end
|
|||
When /^I parse the tokens and use it obtain user info$/ do
|
||||
client_json = JSON.parse(last_response.body)
|
||||
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
|
||||
end
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class AccountDeleter
|
|||
#user deletions
|
||||
def normal_ar_user_associates_to_delete
|
||||
%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
|
||||
|
||||
def special_ar_user_associations
|
||||
|
|
|
|||
|
|
@ -6,17 +6,19 @@ describe Api::OpenidConnect::ClientsController, type: :controller do
|
|||
it "should return a client id" do
|
||||
post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client",
|
||||
response_types: [], grant_types: [], application_type: "web", contacts: [],
|
||||
logo_uri: "http://test.com/logo.png", client_uri: "http://test.com/client",
|
||||
policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos"
|
||||
logo_uri: "http://example.com/logo.png", client_uri: "http://example.com/client",
|
||||
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)
|
||||
expect(client_json["o_auth_application"]["client_id"].length).to eq(32)
|
||||
expect(client_json["o_auth_application"]["ppid"]).to eq(true)
|
||||
end
|
||||
end
|
||||
context "when redirect uri is missing" do
|
||||
it "should return a invalid_client_metadata error" do
|
||||
post :create, response_types: [], grant_types: [], application_type: "web", contacts: [],
|
||||
logo_uri: "http://test.com/logo.png", client_uri: "http://test.com/client",
|
||||
policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos"
|
||||
logo_uri: "http://example.com/logo.png", client_uri: "http://example.com/client",
|
||||
policy_uri: "http://example.com/policy", tos_uri: "http://example.com/tos"
|
||||
client_json = JSON.parse(response.body)
|
||||
expect(client_json["error"]).to have_content("invalid_client_metadata")
|
||||
end
|
||||
|
|
@ -24,8 +26,9 @@ describe Api::OpenidConnect::ClientsController, type: :controller do
|
|||
context "when redirect client_name is missing" do
|
||||
it "should return a invalid_client_metadata error" do
|
||||
post :create, redirect_uris: ["http://localhost"], response_types: [], grant_types: [],
|
||||
application_type: "web", contacts: [], logo_uri: "http://test.com/logo.png",
|
||||
client_uri: "http://test.com/client", policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos"
|
||||
application_type: "web", contacts: [], logo_uri: "http://example.com/logo.png",
|
||||
client_uri: "http://example.com/client", policy_uri: "http://example.com/policy",
|
||||
tos_uri: "http://example.com/tos"
|
||||
client_json = JSON.parse(response.body)
|
||||
expect(client_json["error"]).to have_content("invalid_client_metadata")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,10 +18,17 @@ describe Api::OpenidConnect::DiscoveryController, type: :controller do
|
|||
end
|
||||
|
||||
describe "#configuration" do
|
||||
it "should have the issuer as the root url" do
|
||||
before do
|
||||
get :configuration
|
||||
end
|
||||
it "should have the issuer as the root url" do
|
||||
json_body = JSON.parse(response.body)
|
||||
expect(json_body["issuer"]).to eq("http://test.host/")
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe Api::V0::UsersController do
|
||||
describe Api::OpenidConnect::UserInfoController do
|
||||
# TODO: Replace with factory
|
||||
let!(:client) do
|
||||
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
|
||||
let(:auth_with_read) do
|
||||
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
|
||||
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["profile"]).to eq(File.join(AppConfig.environment.url, "people", alice.guid).to_s)
|
||||
end
|
||||
|
|
@ -3,7 +3,8 @@ require "spec_helper"
|
|||
describe Api::OpenidConnect::TokenEndpoint, type: :request do
|
||||
let!(:client) do
|
||||
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
|
||||
let!(:auth) {
|
||||
Api::OpenidConnect::Authorization.find_or_create_by(
|
||||
|
|
@ -28,6 +29,8 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do
|
|||
encoded_id_token = json["id_token"]
|
||||
decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token,
|
||||
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
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue