diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb index 15ce387387debdeb14a339a13782e746ee6550ff..7c92ecbd79566add8aced1a2c987545f607ccc54 100644 --- a/app/controllers/api/openid_connect/discovery_controller.rb +++ b/app/controllers/api/openid_connect/discovery_controller.rb @@ -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, diff --git a/app/controllers/api/openid_connect/user_info_controller.rb b/app/controllers/api/openid_connect/user_info_controller.rb index 9fb79226d5c0365a936b3861fc465755804cab9e..62de2389bf8c2a90b7b6f4acc4e2549dca0b8905 100644 --- a/app/controllers/api/openid_connect/user_info_controller.rb +++ b/app/controllers/api/openid_connect/user_info_controller.rb @@ -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 diff --git a/app/models/api/openid_connect/id_token.rb b/app/models/api/openid_connect/id_token.rb index 15becc1f8605eeedf76df8cca7a89a563882c525..2855a38b0aae1f94068dfcf92ce56963e70c2ff2 100644 --- a/app/models/api/openid_connect/id_token.rb +++ b/app/models/api/openid_connect/id_token.rb @@ -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 diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 3cc2dadbcb6749a3336af7348db460edba7cc3ba..5ecf8ea69ca39244246f98de8f0e84718b908099 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -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 diff --git a/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb b/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb new file mode 100644 index 0000000000000000000000000000000000000000..06c115dafeaac6a3cbd671b22ae581048254fdfd --- /dev/null +++ b/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb @@ -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 diff --git a/app/models/user.rb b/app/models/user.rb index a7b58882c2f1f3c1136ff2f1e1623451f15e8d8d..ec009aa473d124053d86260fd98f53834aeee0bb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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" diff --git a/app/serializers/user_info_serializer.rb b/app/serializers/user_info_serializer.rb index 3f042380de394836930b7589857aa52c6f06c5a2..67cf3db112705dc6512bb5f4497087ca670de14f 100644 --- a/app/serializers/user_info_serializer.rb +++ b/app/serializers/user_info_serializer.rb @@ -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 diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb index e9452a6c77f1bc4d081f4ffa6af8512f2b3c2cd8..874122eb2b17314d6b13c5a56f845f6649bfdbbc 100644 --- a/db/migrate/20150613202109_create_o_auth_applications.rb +++ b/db/migrate/20150613202109_create_o_auth_applications.rb @@ -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 diff --git a/db/migrate/20150708154727_create_scope.rb b/db/migrate/20150708154727_create_scope.rb index 8d2c51c0282a67ed4cc7ca37937b1b1bbed0f456..afdc320f63ea3ccf301db292407291a6b1b5e651 100644 --- a/db/migrate/20150708154727_create_scope.rb +++ b/db/migrate/20150708154727_create_scope.rb @@ -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 diff --git a/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb b/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb new file mode 100644 index 0000000000000000000000000000000000000000..125b95bfc994174ff6d1053cd9410371838f4390 --- /dev/null +++ b/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 21bb99e43b03130da2eea7f43e0579f82ba98323..09519a9e42f88f65cf22e4b0d98684cafab55a5b 100644 --- a/db/schema.rb +++ b/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 @@ -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 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.string "client_name", limit: 255 - 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 "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.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "user_id", limit: 4 + t.string "client_id", limit: 255 + t.string "client_secret", limit: 255 + t.string "client_name", limit: 255 + t.string "redirect_uris", limit: 255 + t.string "response_types", limit: 255 + t.string "grant_types", 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 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", ["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 diff --git a/features/step_definitions/auth_code_steps.rb b/features/step_definitions/auth_code_steps.rb index a34c284d959f6e51505e73ac3616377de7e5d63e..92a1c93d87a36b61e8f9109dc61c9d287c51397e 100644 --- a/features/step_definitions/auth_code_steps.rb +++ b/features/step_definitions/auth_code_steps.rb @@ -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 diff --git a/lib/account_deleter.rb b/lib/account_deleter.rb index 10dee342ea79696831715a25c146d842f424d4e0..4d03351008c406f3f0316c5a123c71de04fffd8f 100644 --- a/lib/account_deleter.rb +++ b/lib/account_deleter.rb @@ -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 diff --git a/spec/controllers/api/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb index 5326fa32df9d2a0e0de35ad3159b5b3f3c05d08e..e685aeb475980a300414c3c387934cf384551d30 100644 --- a/spec/controllers/api/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb @@ -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 diff --git a/spec/controllers/api/openid_connect/discovery_controller_spec.rb b/spec/controllers/api/openid_connect/discovery_controller_spec.rb index f71126462a6e60e5ca8580c5090e0776ce6c47e1..0233ae180a98737bc5a3c4c6ec69cbd8feacd8fe 100644 --- a/spec/controllers/api/openid_connect/discovery_controller_spec.rb +++ b/spec/controllers/api/openid_connect/discovery_controller_spec.rb @@ -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 diff --git a/spec/integration/api/users_controller_spec.rb b/spec/integration/api/user_info_controller_spec.rb similarity index 68% rename from spec/integration/api/users_controller_spec.rb rename to spec/integration/api/user_info_controller_spec.rb index 7b13f738fee8f01e1bed34c984aa78656f35b727..ad47b486b528d8650312a7b93d266c3b5c4c6716 100644 --- a/spec/integration/api/users_controller_spec.rb +++ b/spec/integration/api/user_info_controller_spec.rb @@ -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 diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index 69228bcaf93fdac1bd6171815214aa36c7d86c29..d712327d088c1564e306fe339ed872e8a558534f 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -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