This commit is contained in:
Leo McArdle 2017-12-20 14:23:07 +00:00
Родитель fbe89af594 159942aed4
Коммит 12531fb8d8
24 изменённых файлов: 586 добавлений и 123 удалений

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

@ -11,7 +11,6 @@ module MozillaIAM
def create def create
mapping = GroupMapping.new(group_mappings_params) mapping = GroupMapping.new(group_mappings_params)
mapping.authoritative = false if params[:authoritative].nil?
mapping.group = Group.find_by(name: params[:group_name]) mapping.group = Group.find_by(name: params[:group_name])
mapping.save! mapping.save!
render json: success_json render json: success_json
@ -40,8 +39,7 @@ module MozillaIAM
def group_mappings_params def group_mappings_params
params.permit( params.permit(
:id, :id,
:iam_group_name, :iam_group_name
:authoritative
) )
end end

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

@ -2,8 +2,7 @@ module MozillaIAM
class GroupMappingSerializer < ApplicationSerializer class GroupMappingSerializer < ApplicationSerializer
attributes :id, attributes :id,
:group_name, :group_name,
:iam_group_name, :iam_group_name
:authoritative
def group_name def group_name
object.group.name object.group.name

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

@ -4,8 +4,7 @@ const Mapping = Ember.Object.extend({
asJSON () { asJSON () {
return { return {
group_name: this.get('group_name'), group_name: this.get('group_name'),
iam_group_name: this.get('iam_group_name'), iam_group_name: this.get('iam_group_name')
authoritative: this.get('authoritative')
} }
}, },

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

@ -10,11 +10,6 @@
{{text-field name='iam_group_name' value=model.iam_group_name}} {{text-field name='iam_group_name' value=model.iam_group_name}}
</div> </div>
<div>
<label for='authoritative'>Authoritative IAM Group?</label>
{{input type='checkbox' name='authoritative' checked=model.authoritative}}
</div>
<div class='buttons'> <div class='buttons'>
<button {{action 'save'}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button> <button {{action 'save'}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button>
<button {{action 'destroy'}} class='btn btn-danger'>{{fa-icon 'trash-o'}}{{i18n 'admin.customize.delete'}}</button> <button {{action 'destroy'}} class='btn btn-danger'>{{fa-icon 'trash-o'}}{{i18n 'admin.customize.delete'}}</button>

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

@ -4,7 +4,6 @@
<th>ID</th> <th>ID</th>
<th>Discourse Group</th> <th>Discourse Group</th>
<th>IAM Group</th> <th>IAM Group</th>
<th>Authoritative?</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -13,7 +12,6 @@
<td>{{#link-to 'adminPlugins.mozilla-iam.mappings.edit' mapping}}{{mapping.id}}{{/link-to}}</td> <td>{{#link-to 'adminPlugins.mozilla-iam.mappings.edit' mapping}}{{mapping.id}}{{/link-to}}</td>
<td>{{mapping.group_name}}</td> <td>{{mapping.group_name}}</td>
<td>{{mapping.iam_group_name}}</td> <td>{{mapping.iam_group_name}}</td>
<td>{{mapping.authoritative}}</td>
</tr> </tr>
{{/each}} {{/each}}
</tbody> </tbody>

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

@ -0,0 +1,26 @@
<section class='details'>
<h1>Mozilla IAM</h1>
<div class='display-row'>
<div class='field'>User ID</div>
<div class='value'>
{{#if model.mozilla_iam.uid}}
{{model.mozilla_iam.uid}}
{{else}}
&mdash;
{{/if}}
</div>
</div>
<div class='display-row'>
<div class='field'>Last Refresh</div>
<div class='value'>
{{#if model.mozilla_iam.last_refresh}}
{{model.mozilla_iam.last_refresh}}
{{else}}
&mdash;
{{/if}}
</div>
</div>
</section>

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

@ -4,6 +4,7 @@ en:
site_settings: site_settings:
categories: categories:
auth0: 'Auth0' auth0: 'Auth0'
mozilla_iam: 'Mozilla IAM'
mozilla_iam: mozilla_iam:
mappings: mappings:
title: 'IAM Group Mappings' title: 'IAM Group Mappings'

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

@ -3,3 +3,5 @@ en:
auth0_domain: 'You auth0 domain. This is typically <account>.auth0.com.' auth0_domain: 'You auth0 domain. This is typically <account>.auth0.com.'
auth0_client_id: 'Your auth0 client id. Find it on the application settings on the Auth0 dashboard.' auth0_client_id: 'Your auth0 client id. Find it on the application settings on the Auth0 dashboard.'
auth0_client_secret: 'Your auth0 client secret. Find it on the application settings on the Auth0 dashboard.' auth0_client_secret: 'Your auth0 client secret. Find it on the application settings on the Auth0 dashboard.'
mozilla_iam_person_api_url: 'URL to the Mozilla IAM Person API endpoint'
mozilla_iam_person_api_aud: 'Audience for the Mozilla IAM Person API'

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

@ -8,3 +8,10 @@ auth0:
auth0_client_secret: auth0_client_secret:
default: '' default: ''
shadowed_by_global: true shadowed_by_global: true
mozilla_iam:
mozilla_iam_person_api_url:
default: 'https://uhbz4h3wa8.execute-api.us-west-2.amazonaws.com/prod'
shadowed_by_global: true
mozilla_iam_person_api_aud:
default: 'https://person-api.sso.mozilla.com'
shadowed_by_global: true

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

@ -0,0 +1,5 @@
class RemoveAuthoritativeFromMozillaIAMGroupMappings < ActiveRecord::Migration[5.1]
def change
remove_column :mozilla_iam_group_mappings, :authoritative, :boolean
end
end

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

@ -1,6 +1,8 @@
require_relative 'mozilla_iam/engine' require_relative 'mozilla_iam/engine'
require_relative 'mozilla_iam/api' require_relative 'mozilla_iam/api'
require_relative 'mozilla_iam/person_api'
require_relative 'mozilla_iam/management_api'
require_relative 'mozilla_iam/application_extensions' require_relative 'mozilla_iam/application_extensions'
require_relative 'mozilla_iam/authenticator' require_relative 'mozilla_iam/authenticator'
require_relative 'mozilla_iam/jwks' require_relative 'mozilla_iam/jwks'

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

@ -1,74 +1,75 @@
module MozillaIAM module MozillaIAM
class API class API
class << self def initialize(config)
@client_id = config[:client_id] || SiteSetting.auth0_client_id
@client_secret = config[:client_secret] || SiteSetting.auth0_client_secret
@token_endpoint = config[:token_endpoint] || "https://#{SiteSetting.auth0_domain}/oauth/token"
@url = config[:url]
raise ArgumentError, "no url in config" unless @url
@aud = config[:aud]
raise ArgumentError, "no aud in config" unless @aud
end
def user(uid) private
Rails.logger.info("Auth0 API query for user_id: #{uid}")
profile = get("users/#{uid}", fields: 'app_metadata') def get(path, params = false)
{ app_metadata: {} }.merge(profile)[:app_metadata] path = URI.encode(path)
uri = URI("#{@url}/#{path}")
uri.query = URI.encode_www_form(params) if params
req = Net::HTTP::Get.new(uri)
req['Authorization'] = "Bearer #{access_token}"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(req)
end end
private if res.code == '200'
MultiJson.load(res.body, symbolize_keys: true)
def get(path, params = false) else
path = URI.encode(path) {}
uri = URI("https://#{SiteSetting.auth0_domain}/api/v2/#{path}")
uri.query = URI.encode_www_form(params) if params
req = Net::HTTP::Get.new(uri)
req['Authorization'] = "Bearer #{access_token}"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(req)
end
if res.code == '200'
MultiJson.load(res.body, symbolize_keys: true)
else
{}
end
end end
end
def access_token def access_token
api_creds = ::PluginStore.get('mozilla-iam', 'api_creds') api_token = ::PluginStore.get('mozilla-iam', "#{@aud}_token")
if api_creds.nil? || api_creds[:exp] < Time.now.to_i + 60 if api_token.nil? || api_token[:exp] < Time.now.to_i + 60
refresh_token refresh_token
else else
api_creds[:access_token] api_token[:access_token]
end
end end
end
def refresh_token def refresh_token
token = fetch_token token = fetch_token
payload = verify_token(token) payload = verify_token(token)
::PluginStore.set('mozilla-iam', 'api_creds', { access_token: token, exp: payload['exp'] }) ::PluginStore.set('mozilla-iam', "#{@aud}_token", { access_token: token, exp: payload['exp'] })
token token
end end
def fetch_token def fetch_token
response = response =
Faraday.post( Faraday.post(
'https://' + SiteSetting.auth0_domain + '/oauth/token', @token_endpoint,
{ {
grant_type: 'client_credentials', grant_type: 'client_credentials',
client_id: SiteSetting.auth0_client_id, client_id: @client_id,
client_secret: SiteSetting.auth0_client_secret, client_secret: @client_secret,
audience: 'https://' + SiteSetting.auth0_domain + '/api/v2/' audience: @aud
} }
) )
MultiJson.load(response.body)['access_token'] MultiJson.load(response.body)['access_token']
end end
def verify_token(token) def verify_token(token)
payload, header = payload, header =
JWT.decode( JWT.decode(
token, token,
aud: 'https://' + SiteSetting.auth0_domain + '/api/v2/', aud: @aud,
sub: SiteSetting.auth0_client_id + '@clients', sub: @client_id + '@clients',
verify_sub: true verify_sub: true
) )
payload payload
end
end end
end end
end end

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

@ -0,0 +1,17 @@
module MozillaIAM
class ManagementAPI < API
def initialize(config={})
config = {
url: "https://#{SiteSetting.auth0_domain}/api/v2",
aud: "https://#{SiteSetting.auth0_domain}/api/v2/"
}.merge(config)
super(config)
end
def profile(uid)
get("users/#{uid}")
end
end
end

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

@ -0,0 +1,18 @@
module MozillaIAM
class PersonAPI < API
def initialize(config={})
config = {
url: SiteSetting.mozilla_iam_person_api_url,
aud: SiteSetting.mozilla_iam_person_api_aud
}.merge(config)
super(config)
end
def profile(uid)
profile = get("profile/#{uid}")
MultiJson.load(profile[:body], symbolize_keys: true)
end
end
end

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

@ -23,10 +23,14 @@ module MozillaIAM
end end
end end
def groups
Array(mgmt_profile[:groups]) | Array(mgmt_profile.dig(:app_metadata, :groups))
end
private private
def profile def mgmt_profile
@profile ||= API.user(@uid) @mgmt_profile ||= ManagementAPI.new.profile(@uid)
end end
def last_refresh def last_refresh
@ -47,16 +51,7 @@ module MozillaIAM
def update_groups def update_groups
GroupMapping.all.each do |mapping| GroupMapping.all.each do |mapping|
if mapping.authoritative if groups.include?(mapping.iam_group_name)
in_group =
profile[:authoritativeGroups]&.any? do |authoritative_group|
authoritative_group[:name] == mapping.iam_group_name
end
else
in_group = profile[:groups]&.include?(mapping.iam_group_name)
end
if in_group
add_to_group(mapping.group) add_to_group(mapping.group)
else else
remove_from_group(mapping.group) remove_from_group(mapping.group)

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

@ -1,6 +1,6 @@
# name: mozilla-iam # name: mozilla-iam
# about: A plugin to integrate Discourse with Mozilla's Identity and Access Management (IAM) system # about: A plugin to integrate Discourse with Mozilla's Identity and Access Management (IAM) system
# version: 0.1.1 # version: 0.1.3
# authors: Leo McArdle # authors: Leo McArdle
# url: https://github.com/mozilla/discourse-mozilla-iam # url: https://github.com/mozilla/discourse-mozilla-iam
@ -22,3 +22,15 @@ auth_provider(title: 'Mozilla',
message: 'Log In / Sign Up', message: 'Log In / Sign Up',
authenticator: MozillaIAM::Authenticator.new('auth0', trusted: true), authenticator: MozillaIAM::Authenticator.new('auth0', trusted: true),
full_screen_login: true) full_screen_login: true)
after_initialize do
add_to_serializer(:AdminDetailedUser, :mozilla_iam, false) do
object.custom_fields.select do |k, v|
k.start_with?('mozilla_iam')
end.map do |k, v|
[k.sub('mozilla_iam_', ''), v]
end.to_h
end
end

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

@ -0,0 +1,152 @@
require_relative "../../iam_helper"
describe MozillaIAM::API do
let(:config) do
return {
client_id: "abc",
client_secret: "def",
token_endpoint: "https://example.com/oauth/token",
url: "https://example.com/api",
aud: "example.com"
}
end
let(:api) { MozillaIAM::API.new(config) }
context "#initialize" do
before do
SiteSetting.auth0_client_id = "xyz"
SiteSetting.auth0_client_secret = "zyx"
SiteSetting.auth0_domain = "foobar.com"
end
it "uses config options" do
expect(api.instance_variable_get(:@client_id)).to eq config[:client_id]
expect(api.instance_variable_get(:@client_secret)).to eq config[:client_secret]
expect(api.instance_variable_get(:@token_endpoint)).to eq config[:token_endpoint]
expect(api.instance_variable_get(:@url)).to eq config[:url]
expect(api.instance_variable_get(:@aud)).to eq config[:aud]
end
it "uses default options if none are set" do
config = {
url: "https://example.com/api",
aud: "example.com"
}
api = MozillaIAM::API.new(config)
expect(api.instance_variable_get(:@client_id)).to eq SiteSetting.auth0_client_id
expect(api.instance_variable_get(:@client_secret)).to eq SiteSetting.auth0_client_secret
expect(api.instance_variable_get(:@token_endpoint)).to eq "https://#{SiteSetting.auth0_domain}/oauth/token"
expect(api.instance_variable_get(:@url)).to eq config[:url]
expect(api.instance_variable_get(:@aud)).to eq config[:aud]
end
it "throws error if url isn't specified" do
config = { aud: "example.com" }
expect { MozillaIAM::API.new(config) }.to raise_error "no url in config"
end
it "throws error if aud isn't specified" do
config = { url: "https://example.com/api" }
expect { MozillaIAM::API.new(config) }.to raise_error "no aud in config"
end
end
context "#get" do
let(:res_success) { return { status: 200, body: '{"success":"true"}' } }
before do
api.expects(:access_token).returns('supersecret')
end
it "should get the right path" do
stub_request(:get, "https://example.com/api/right_path").to_return(res_success)
expect(api.send(:get, "right_path")[:success]).to eq "true"
end
it "should set the Authorization header" do
stub_request(:get, "https://example.com/api/").with(headers: {
"Authorization": "Bearer supersecret"
}).to_return(res_success)
expect(api.send(:get, "")[:success]).to eq "true"
end
it "should send parameters" do
stub_request(:get, "https://example.com/api/?foo=bar").to_return(res_success)
expect(api.send(:get, "", foo: 'bar')[:success]).to eq "true"
end
it "should return an empty hash if the the status code isn't 200" do
stub_request(:get, "https://example.com/api/").to_return(status: 403)
expect(api.send(:get, "")).to eq({})
end
end
context "#access_token" do
before do
api.stubs(:refresh_token).returns("refreshed_secret")
end
it "fetches the token from aud prefix" do
::PluginStore.set('mozilla-iam', "example.com_token", exp: Time.now.to_i + 1000, access_token: "secret")
expect(api.send(:access_token)).to eq "secret"
end
it "refreshes the token if there's no saved token" do
expect(api.send(:access_token)).to eq "refreshed_secret"
end
it "refreshes the token if the saved token is expired" do
::PluginStore.set('mozilla-iam', "example.com_token", exp: Time.now.to_i, access_token: "secret")
expect(api.send(:access_token)).to eq "refreshed_secret"
end
end
context "#refresh_token" do
it "stores token with aud prefix" do
api.expects(:fetch_token).returns("token")
api.expects(:verify_token).with("token").returns({ "exp" => "exp" })
token = api.send(:refresh_token)
saved_token = ::PluginStore.get('mozilla-iam', "example.com_token")
expect(token).to eq "token"
expect(saved_token[:access_token]).to eq "token"
expect(saved_token[:exp]).to eq "exp"
end
end
context "#fetch_token" do
it "fetches token from token_endpoint" do
stub_request(:post, "https://example.com/oauth/token").with(
body: {
grant_type: "client_credentials",
client_id: "abc",
client_secret: "def",
audience: "example.com"
}
).to_return(status: 200, body: '{"access_token":"fetched_token"}')
token = api.send(:fetch_token)
expect(token).to eq "fetched_token"
end
end
context "#verify_token" do
it "returns verified token" do
SiteSetting.auth0_domain = "example.com"
MozillaIAM::JWKS.expects(:public_key).with("jwt").returns("public_key")
::JWT.expects(:decode).with("jwt", "public_key", true, {
algorithm: "RS256",
iss: "https://example.com/",
aud: "example.com",
sub: "abc@clients",
verify_iss: true,
verify_iat: true,
verify_aud: true,
verify_sub: true,
verify_iss: true
}).returns(["verified_token", "header"])
token = api.send(:verify_token, "jwt")
expect(token).to eq "verified_token"
end
end
end

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

@ -0,0 +1,27 @@
require_relative "../../iam_helper"
describe MozillaIAM::ManagementAPI do
let(:api) { MozillaIAM::ManagementAPI.new }
before do
SiteSetting.auth0_domain = "foobar.com"
end
context "#initialize" do
it "sets url and aud based on auth0_domain" do
expect(api.instance_variable_get(:@url)).to eq "https://foobar.com/api/v2"
expect(api.instance_variable_get(:@aud)).to eq "https://foobar.com/api/v2/"
end
end
context "#profile" do
it "returns the management api profile" do
api.expects(:get).with("users/uid").returns("profile")
expect(api.profile("uid")).to eq "profile"
end
it "returns an empty hash if a profile doesn't exist" do
api.expects(:get).with("users/uid").returns({})
expect(api.profile("uid")).to eq({})
end
end
end

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

@ -0,0 +1,28 @@
require_relative "../../iam_helper"
describe MozillaIAM::PersonAPI do
let(:api) { MozillaIAM::PersonAPI.new }
before do
SiteSetting.mozilla_iam_person_api_url = "https://person.com"
SiteSetting.mozilla_iam_person_api_aud = "person.com"
end
context "#initialize" do
it "sets url and aud based on SiteSetting" do
expect(api.instance_variable_get(:@url)).to eq "https://person.com"
expect(api.instance_variable_get(:@aud)).to eq "person.com"
end
end
context "#profile" do
it "returns the profile for a specific user" do
api.expects(:get).with("profile/uid").returns(body: '{"profile":"profile"}')
expect(api.profile("uid")[:profile]).to eq "profile"
end
it "returns an empty hash if a profile doesn't exist" do
api.expects(:get).with("profile/uid").returns(body: '{}')
expect(api.profile("uid")).to eq({})
end
end
end

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

@ -1,9 +1,19 @@
require_relative '../../iam_helper' require_relative '../../iam_helper'
describe MozillaIAM::Profile do describe MozillaIAM::Profile do
let(:user) { Fabricate(:user) }
let(:profile) { MozillaIAM::Profile.new(user, "uid") }
context '.refresh' do context '.refresh' do
it "refreshes a user who already has a profile" do
profile
MozillaIAM::Profile.expects(:new).with(user, "uid").returns(profile)
MozillaIAM::Profile.any_instance.expects(:refresh).returns(true)
result = MozillaIAM::Profile.refresh(user)
expect(result).to be true
end
it 'should return nil if user has no profile' do it 'should return nil if user has no profile' do
user = Fabricate(:user)
result = MozillaIAM::Profile.refresh(user) result = MozillaIAM::Profile.refresh(user)
expect(result).to be_nil expect(result).to be_nil
end end
@ -11,23 +21,107 @@ describe MozillaIAM::Profile do
context '#initialize' do context '#initialize' do
it "should save a user's uid" do it "should save a user's uid" do
user = Fabricate(:user) profile
uid = create_uid(user.username) expect(user.custom_fields['mozilla_iam_uid']).to eq("uid")
MozillaIAM::Profile.new(user, uid)
expect(user.custom_fields['mozilla_iam_uid']).to eq(uid)
end end
end end
context '#refresh' do context '#refresh' do
it "should refresh a user's profile if it hasn't been refreshed before" do it "returns #force_refresh if #should_refresh? is true" do
user = Fabricate(:user) profile.expects(:should_refresh?).returns(true)
uid = create_uid(user.username) profile.expects(:last_refresh).never
profile.expects(:force_refresh).once.returns(true)
expect(profile.refresh).to be true
end
result = MozillaIAM::Profile.new(user, uid).refresh it "returns #last_refresh if #should_refresh? is false" do
profile.expects(:should_refresh?).returns(false)
profile.expects(:force_refresh).never
profile.expects(:last_refresh).once.returns(true)
expect(profile.refresh).to be true
end
end
expect(result).to be_within(5.seconds).of Time.now context "#force_refresh" do
it "calls update_groups" do
profile.expects(:update_groups)
profile.force_refresh
end
it "sets the last refresh to now and returns it" do
profile.expects(:set_last_refresh).with() { |t| t.between?(Time.now() - 5, Time.now()) }.returns("time now")
expect(profile.force_refresh).to eq "time now"
end
end
context "#groups" do
it "merges app_metadata.groups into groups from management api" do
profile.expects(:mgmt_profile).at_least_once.returns(groups: ['a'], app_metadata: { groups: ['b', 'a', 'c'] })
expect(profile.groups).to eq ['a', 'b', 'c']
end
it "returns app_metadata.groups when groups is nil" do
profile.expects(:mgmt_profile).at_least_once.returns(app_metadata: { groups: ['a', 'b', 'c'] })
expect(profile.groups).to eq ['a', 'b', 'c']
end
it "returns groups when app_metadata is nil" do
profile.expects(:mgmt_profile).at_least_once.returns(groups: ['a', 'b', 'c'])
expect(profile.groups).to eq ['a', 'b', 'c']
end
it "returns empty array when groups and app_metadata are nil" do
profile.expects(:mgmt_profile).at_least_once.returns({})
expect(profile.groups).to eq []
end
end
context "#mgmt_profile" do
it "returns a user's profile from the Management API and stores it in an instance variable" do
MozillaIAM::ManagementAPI.any_instance.expects(:profile).with("uid").returns("profile")
expect(profile.send(:mgmt_profile)).to eq "profile"
expect(profile.instance_variable_get(:@mgmt_profile)).to eq "profile"
end
end
context "#last_refresh" do
it "returns a user's last refreshed time if set and stores it in an instance variable" do
time_string = Time.now().to_s
time = Time.parse(time_string)
profile.expects(:get).returns(time_string)
expect(profile.send(:last_refresh)).to eq time
expect(profile.instance_variable_get(:@last_refresh)).to eq time
end
it "returns nil if a user's has no last refresh time" do
profile.expects(:get).returns(nil)
expect(profile.send(:last_refresh)).to be_nil
end
end
context "#set_last_refresh" do
it "stores a time and stores it in an instance variable" do
time = Time.now()
profile.expects(:set).with(:last_refresh, time).returns(time)
expect(profile.send(:set_last_refresh, time)).to eq time
expect(profile.instance_variable_get(:@last_refresh)).to eq time
end
end
context "#should_refresh?" do
it "returns true if last_refresh is nil" do
profile.expects(:last_refresh).returns(nil)
expect(profile.send(:should_refresh?)).to be true
end
it "returns true if last_refresh was over 15 minutes ago" do
profile.expects(:last_refresh).at_least_once.returns(Time.now() - 16.minutes)
expect(profile.send(:should_refresh?)).to be true
end
it "returns false if last_refresh was within 15 minutes ago" do
profile.expects(:last_refresh).at_least_once.returns(Time.now() - 14.minutes)
expect(profile.send(:should_refresh?)).to be false
end end
end end
@ -38,55 +132,106 @@ describe MozillaIAM::Profile do
before do before do
MozillaIAM::GroupMapping.new(iam_group_name: 'iam_group', MozillaIAM::GroupMapping.new(iam_group_name: 'iam_group',
authoritative: false,
group: group).save! group: group).save!
end end
it 'should remove a user from a mapped group' do it 'should remove a user from a mapped group' do
profile.expects(:groups).returns([])
group.users << user group.users << user
expect(group.users.count).to eq 1 expect(group.users.count).to eq 1
stub_api_users_request(uid, groups: []) profile.send(:update_groups)
MozillaIAM::Profile.new(user, uid).refresh
expect(group.users.count).to eq 0 expect(group.users.count).to eq 0
end end
it 'should add a user to a mapped group' do it 'should add a user to a mapped group' do
profile.expects(:groups).returns(['iam_group'])
expect(group.users.count).to eq 0 expect(group.users.count).to eq 0
stub_api_users_request(uid, groups: ['iam_group']) profile.send(:update_groups)
MozillaIAM::Profile.new(user, uid).refresh
expect(group.users.count).to eq 1 expect(group.users.count).to eq 1
end end
it 'should work if groups attribute is undefined' do it 'should work if groups attribute is undefined' do
expect(group.users.count).to eq 0 expect(group.users.count).to eq 0
stub_api_users_request(uid, {}) MozillaIAM::ManagementAPI.any_instance.expects(:profile).returns(groups: nil, app_metadata: { groups: nil })
MozillaIAM::Profile.new(user, uid).refresh profile.send(:update_groups)
expect(group.users.count).to eq 0 expect(group.users.count).to eq 0
end end
it 'should work if groups attribute is an empty string' do it 'should work if groups attribute is an empty string' do
expect(group.users.count).to eq 0 expect(group.users.count).to eq 0
stub_api_users_request(uid, groups: '') MozillaIAM::ManagementAPI.any_instance.expects(:profile).returns(groups: '', app_metadata: { groups: '' })
MozillaIAM::Profile.new(user, uid).refresh profile.send(:update_groups)
expect(group.users.count).to eq 0 expect(group.users.count).to eq 0
end end
it 'should work if groups attribute is "None"' do it 'should work if groups attribute is "None"' do
expect(group.users.count).to eq 0 expect(group.users.count).to eq 0
stub_api_users_request(uid, groups: 'None') MozillaIAM::ManagementAPI.any_instance.expects(:profile).returns(groups: "None", app_metadata: { groups: "None" })
MozillaIAM::Profile.new(user, uid).refresh profile.send(:update_groups)
expect(group.users.count).to eq 0 expect(group.users.count).to eq 0
end end
end end
context "#add_to_group" do
it "adds the user to a group" do
group = Fabricate(:group)
profile.send(:add_to_group, group)
expect(group.users.first).to eq user
end
end
context "#remove_from_group" do
it "removes the user from a group" do
group = Fabricate(:group, users: [user])
profile.send(:remove_from_group, group)
expect(group.users.count).to eq 0
end
it "doesn't error out when removing a user from a group they're not in" do
group = Fabricate(:group)
profile.send(:remove_from_group, group)
expect(group.users.count).to eq 0
end
end
context ".get" do
it "returns a value for the key" do
described_class.set(user, "key", "value")
expect(described_class.get(user, "key")).to eq "value"
end
end
context "#get" do
it "calls .get with the user" do
described_class.expects(:get).with(user, "key").returns("value")
expect(profile.send(:get, "key")).to eq "value"
end
end
context ".set" do
it "saves the value for a key and returns it" do
value = described_class.set(user, "key", "value")
expect(value).to eq "value"
expect(described_class.get(user, "key")).to eq "value"
end
end
context "#set" do
it "calls .set with the user" do
profile
described_class.expects(:set).with(user, "key", "value").returns("value")
expect(profile.send(:set, "key", "value")).to eq "value"
end
end
end end

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

@ -3,9 +3,7 @@ require_relative '../iam_helper'
describe MozillaIAM::GroupMapping do describe MozillaIAM::GroupMapping do
it 'should be destroyed when associated group is destroyed' do it 'should be destroyed when associated group is destroyed' do
group = Fabricate(:group) group = Fabricate(:group)
mapping = MozillaIAM::GroupMapping.new(iam_group_name: 'iam_group', mapping = MozillaIAM::GroupMapping.new(iam_group_name: 'iam_group', group: group)
authoritative: false,
group: group)
mapping.save! mapping.save!
mapping.reload mapping.reload

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

@ -15,7 +15,6 @@ describe MozillaIAM do
before do before do
MozillaIAM::GroupMapping.new(iam_group_name: 'iam_group', MozillaIAM::GroupMapping.new(iam_group_name: 'iam_group',
authoritative: false,
group: group).save! group: group).save!
TopicUser.change(user.id, topic.id, notification_level: TopicUser.notification_levels[:watching]) TopicUser.change(user.id, topic.id, notification_level: TopicUser.notification_levels[:watching])
user.custom_fields['mozilla_iam_uid'] = uid user.custom_fields['mozilla_iam_uid'] = uid
@ -24,7 +23,7 @@ describe MozillaIAM do
end end
context 'when user in correct IAM group' do context 'when user in correct IAM group' do
before { stub_api_users_request(uid, groups: ['iam_group']) } before { stub_management_api_profile_request(uid, groups: ['iam_group']) }
it 'refreshes the user profile' do it 'refreshes the user profile' do
PostAlerter.post_created(reply) PostAlerter.post_created(reply)
@ -52,7 +51,7 @@ describe MozillaIAM do
end end
context 'when user removed from IAM group' do context 'when user removed from IAM group' do
before { stub_api_users_request(uid, groups: []) } before { stub_management_api_profile_request(uid, groups: []) }
it 'refreshes the user profile' do it 'refreshes the user profile' do
PostAlerter.post_created(reply) PostAlerter.post_created(reply)

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

@ -0,0 +1,28 @@
require_relative '../iam_helper'
describe AdminDetailedUserSerializer do
let(:user) { Fabricate(:user) }
let(:json) { AdminDetailedUserSerializer.new(user, scope: Guardian.new, root:false).as_json }
describe "#mozilla_iam" do
it "should contain 'mozilla_iam' prefixed custom fields" do
mozilla_iam_one = 'Some IAM data'
mozilla_iam_two = 'Some more IAM data'
user.custom_fields['mozilla_iam_one'] = mozilla_iam_one
user.custom_fields['mozilla_iam_two'] = mozilla_iam_two
user.save
mozilla_iam = json[:mozilla_iam]
expect(mozilla_iam['one']).to eq(mozilla_iam_one)
expect(mozilla_iam['two']).to eq(mozilla_iam_two)
end
it "shouldn't contain non-'mozilla_iam' prefixed custom fields" do
user.custom_fields['other_custom_fields'] = 'some data'
user.save
expect(json[:mozilla_iam]).to be_empty
end
end
end

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

@ -32,6 +32,10 @@ module IAMHelpers
.to_return(status: 200, body: create_jwks) .to_return(status: 200, body: create_jwks)
end end
def create_jwt(payload, header)
JWT.encode(payload, private_key, 'RS256', header)
end
def create_id_token(user, additional_payload = {}, additional_header = {}) def create_id_token(user, additional_payload = {}, additional_header = {})
payload = { payload = {
name: user[:name], name: user[:name],
@ -48,7 +52,7 @@ module IAMHelpers
kid: 'the_best_key' kid: 'the_best_key'
}.merge(additional_header) }.merge(additional_header)
JWT.encode(payload, private_key, 'RS256', header_fields) create_jwt(payload, header_fields)
end end
def create_uid(username) def create_uid(username)
@ -71,13 +75,13 @@ module IAMHelpers
authenticate_with_id_token create_id_token(user) authenticate_with_id_token create_id_token(user)
end end
def stub_oauth_token_request def stub_oauth_token_request(aud)
stub_jwks_request stub_jwks_request
payload = { payload = {
sub: 'the_best_client_id@clients', sub: 'the_best_client_id@clients',
iss: 'https://auth.mozilla.auth0.com/', iss: 'https://auth.mozilla.auth0.com/',
aud: 'https://auth.mozilla.auth0.com/api/v2/', aud: aud,
exp: Time.now.to_i + 7.days, exp: Time.now.to_i + 7.days,
iat: Time.now.to_i iat: Time.now.to_i
} }
@ -93,10 +97,17 @@ module IAMHelpers
.to_return(status: 200, body: body) .to_return(status: 200, body: body)
end end
def stub_api_users_request(uid, app_metadata) def stub_people_api_profile_request(uid, profile)
stub_oauth_token_request stub_oauth_token_request('https://person-api.sso.mozilla.com')
stub_request(:get, "https://auth.mozilla.auth0.com/api/v2/users/#{uid}?fields=app_metadata") stub_request(:get, "https://uhbz4h3wa8.execute-api.us-west-2.amazonaws.com/prod/profile/#{uid}")
.to_return(status: 200, body: MultiJson.dump(app_metadata: app_metadata)) .to_return(status: 200, body: MultiJson.dump(body: MultiJson.dump(profile)))
end
def stub_management_api_profile_request(uid, profile)
stub_oauth_token_request('https://auth.mozilla.auth0.com/api/v2/')
stub_request(:get, "https://auth.mozilla.auth0.com/api/v2/users/#{uid}")
.to_return(status: 200, body: MultiJson.dump(app_metadata: profile))
end end
end end