Skip to content

Commit

Permalink
BACKPORT: FEATURE: consume CIS webhook and refresh profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoMcA committed Apr 25, 2019
1 parent dfda0ec commit 3b0df9a
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 1 deletion.
30 changes: 30 additions & 0 deletions app/controllers/mozilla_iam/notification_controller.rb
@@ -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
1 change: 1 addition & 0 deletions config/routes.rb
Expand Up @@ -8,4 +8,5 @@
namespace :admin, constraints: AdminConstraint.new do
resources :group_mappings, path: :mappings
end
post :notification, to: "notification#notification"
end
3 changes: 3 additions & 0 deletions config/settings.yml
Expand Up @@ -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
6 changes: 6 additions & 0 deletions lib/mozilla_iam/profile.rb
Expand Up @@ -25,6 +25,12 @@ def refresh(user)
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)
Expand Down
2 changes: 1 addition & 1 deletion plugin.rb
@@ -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

Expand Down
14 changes: 14 additions & 0 deletions spec/components/mozilla_iam/profile_spec.rb
Expand Up @@ -63,6 +63,20 @@
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
Expand Down
165 changes: 165 additions & 0 deletions spec/controllers/mozilla_iam/notification_controller_spec.rb
@@ -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

0 comments on commit 3b0df9a

Please sign in to comment.