BACKPORT: FEATURE: consume CIS webhook and refresh profiles

https://github.com/mozilla/discourse/issues/184
This commit is contained in:
Leo McArdle 2019-04-12 11:20:15 +01:00
Родитель dfda0ec62a
Коммит 3b0df9af18
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 8262833620A64C3F
7 изменённых файлов: 220 добавлений и 1 удалений

Просмотреть файл

@ -0,0 +1,30 @@
module MozillaIAM
class NotificationController < ActionController::Base
def notification
unless ["update", "delete"].include? params[:operation]
return render body: "Unsupported operation", status: 200
end
begin
token = request.headers["Authorization"].sub("Bearer ","")
JWT.decode(
token,
aud: SiteSetting.mozilla_iam_notification_aud
)
rescue => e
return render body: "Invalid JWT", status: 400
end
uid = params[:id]
profile = Profile.find_by_uid(uid)
profile&.force_refresh
Rails.logger.info <<~EOF.gsub(/\n/, ", ")
Mozilla IAM: Successfully refreshed profile for #{uid}
operation: #{params[:operation]}, time: #{params[:time]}
refresh time: #{Time.now.to_i}
EOF
render body: nil, status: 200
end
end
end

Просмотреть файл

@ -8,4 +8,5 @@ MozillaIAM::Engine.routes.draw do
namespace :admin, constraints: AdminConstraint.new do
resources :group_mappings, path: :mappings
end
post :notification, to: "notification#notification"
end

Просмотреть файл

@ -15,3 +15,6 @@ mozilla_iam:
mozilla_iam_person_api_aud:
default: 'https://person-api.sso.mozilla.com'
shadowed_by_global: true
mozilla_iam_notification_aud:
default: "hook.prod.sso.mozilla.com"
shadowed_by_global: true

Просмотреть файл

@ -25,6 +25,12 @@ module MozillaIAM
profile = self.for(user)
profile.refresh unless profile.nil?
end
def find_by_uid(uid)
user = UserCustomField.where(name: "mozilla_iam_uid", value: uid).last&.user
return if user.nil?
return Profile.new(user, uid)
end
end
def initialize(user, uid)

Просмотреть файл

@ -1,6 +1,6 @@
# name: mozilla-iam
# about: A plugin to integrate Discourse with Mozilla's Identity and Access Management (IAM) system
# version: 1.1.7
# version: 1.1.8+cis-webhook
# authors: Leo McArdle
# url: https://github.com/mozilla/discourse-mozilla-iam

Просмотреть файл

@ -63,6 +63,20 @@ describe MozillaIAM::Profile do
end
end
describe ".find_by_uid" do
it "returns a user who has the uid" do
profile
MozillaIAM::Profile.expects(:new).with(user, "uid").returns(profile)
result = described_class.find_by_uid("uid")
expect(result).to eq profile
end
it "returns nil if there's no user with that uid" do
result = described_class.find_by_uid("uid")
expect(result).to be_nil
end
end
context '#initialize' do
it "should save a user's uid" do
profile

Просмотреть файл

@ -0,0 +1,165 @@
require_relative '../../iam_helper'
describe MozillaIAM::NotificationController, type: :request do
describe "#notification" do
let(:user) { Fabricate(:user) }
before do
stub_jwks_request
end
shared_context "no JWT" do
let(:headers) do
{ "Content-Type": "application/json" }
end
end
shared_context "invalid JWT" do
let(:jwt) do
create_jwt({
iss: 'https://auth.mozilla.auth0.com/',
aud: 'nope',
exp: Time.now.to_i + 7.days,
iat: Time.now.to_i,
}, {
kid: 'the_best_key'
})
end
let(:headers) do
{
"Content-Type": "application/json",
"Authorization": "Bearer #{jwt}"
}
end
end
shared_context "valid JWT" do
let(:jwt) do
create_jwt({
iss: 'https://auth.mozilla.auth0.com/',
aud: 'hook.prod.sso.mozilla.com',
exp: Time.now.to_i + 7.days,
iat: Time.now.to_i,
}, {
kid: 'the_best_key'
})
end
let(:headers) do
{
"Content-Type": "application/json",
"Authorization": "Bearer #{jwt}"
}
end
end
shared_examples "does nothing" do
it "does nothing" do
MozillaIAM::Profile.any_instance.expects(:force_refresh).never
post "/mozilla_iam/notification", params: notification.to_json, headers: headers
expect(response.status).to eq 200
end
end
shared_examples "error" do
context "with no JWT" do
include_context "no JWT"
it "errors out" do
post "/mozilla_iam/notification", params: notification.to_json, headers: headers
expect(response.status).to eq 400
expect(response.body).to eq "Invalid JWT"
end
end
context "with an invalid JWT" do
include_context "invalid JWT"
it "errors out" do
post "/mozilla_iam/notification", params: notification.to_json, headers: headers
expect(response.status).to eq 400
expect(response.body).to eq "Invalid JWT"
end
end
end
shared_examples "success" do
context "with a valid JWT" do
include_context "valid JWT"
context "with a user_id which doesn't exist" do
include_examples "does nothing"
end
context "with a user_id which exists" do
before do
user.custom_fields["mozilla_iam_uid"] = notification[:id]
user.save_custom_fields
end
it "refreshes user" do
MozillaIAM::Profile.any_instance.expects(:force_refresh)
post "/mozilla_iam/notification", params: notification.to_json, headers: headers
expect(response.status).to eq 200
end
end
end
end
context "with an update notification" do
let(:notification) do
{
operation: "update",
id: "ad|Mozilla-LDAP|dinomcvouch",
time: Time.now
}
end
include_examples "error"
include_examples "success"
end
context "with a delete notification" do
let(:notification) do
{
operation: "delete",
id: "ad|Mozilla-LDAP|dinomcvouch",
time: Time.now
}
end
include_examples "error"
include_examples "success"
end
context "with a create notification" do
let(:notification) do
{
operation: "create",
id: "ad|Mozilla-LDAP|dinomcvouch",
time: Time.now
}
end
context "with no JWT" do
include_context "no JWT"
include_examples "does nothing"
end
context "with invalid JWT" do
include_context "invalid JWT"
include_examples "does nothing"
end
context "with valid JWT" do
include_context "valid JWT"
include_examples "does nothing"
end
end
end
end