Merge branch 'development'
This commit is contained in:
Коммит
12531fb8d8
|
@ -11,7 +11,6 @@ module MozillaIAM
|
|||
|
||||
def create
|
||||
mapping = GroupMapping.new(group_mappings_params)
|
||||
mapping.authoritative = false if params[:authoritative].nil?
|
||||
mapping.group = Group.find_by(name: params[:group_name])
|
||||
mapping.save!
|
||||
render json: success_json
|
||||
|
@ -40,8 +39,7 @@ module MozillaIAM
|
|||
def group_mappings_params
|
||||
params.permit(
|
||||
:id,
|
||||
:iam_group_name,
|
||||
:authoritative
|
||||
:iam_group_name
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -2,8 +2,7 @@ module MozillaIAM
|
|||
class GroupMappingSerializer < ApplicationSerializer
|
||||
attributes :id,
|
||||
:group_name,
|
||||
:iam_group_name,
|
||||
:authoritative
|
||||
:iam_group_name
|
||||
|
||||
def group_name
|
||||
object.group.name
|
||||
|
|
|
@ -4,8 +4,7 @@ const Mapping = Ember.Object.extend({
|
|||
asJSON () {
|
||||
return {
|
||||
group_name: this.get('group_name'),
|
||||
iam_group_name: this.get('iam_group_name'),
|
||||
authoritative: this.get('authoritative')
|
||||
iam_group_name: this.get('iam_group_name')
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -10,11 +10,6 @@
|
|||
{{text-field name='iam_group_name' value=model.iam_group_name}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for='authoritative'>Authoritative IAM Group?</label>
|
||||
{{input type='checkbox' name='authoritative' checked=model.authoritative}}
|
||||
</div>
|
||||
|
||||
<div class='buttons'>
|
||||
<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>
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
<th>ID</th>
|
||||
<th>Discourse Group</th>
|
||||
<th>IAM Group</th>
|
||||
<th>Authoritative?</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -13,7 +12,6 @@
|
|||
<td>{{#link-to 'adminPlugins.mozilla-iam.mappings.edit' mapping}}{{mapping.id}}{{/link-to}}</td>
|
||||
<td>{{mapping.group_name}}</td>
|
||||
<td>{{mapping.iam_group_name}}</td>
|
||||
<td>{{mapping.authoritative}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</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}}
|
||||
—
|
||||
{{/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}}
|
||||
—
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
|
@ -4,6 +4,7 @@ en:
|
|||
site_settings:
|
||||
categories:
|
||||
auth0: 'Auth0'
|
||||
mozilla_iam: 'Mozilla IAM'
|
||||
mozilla_iam:
|
||||
mappings:
|
||||
title: 'IAM Group Mappings'
|
||||
|
|
|
@ -3,3 +3,5 @@ en:
|
|||
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_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:
|
||||
default: ''
|
||||
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/api'
|
||||
require_relative 'mozilla_iam/person_api'
|
||||
require_relative 'mozilla_iam/management_api'
|
||||
require_relative 'mozilla_iam/application_extensions'
|
||||
require_relative 'mozilla_iam/authenticator'
|
||||
require_relative 'mozilla_iam/jwks'
|
||||
|
|
|
@ -1,74 +1,75 @@
|
|||
module MozillaIAM
|
||||
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)
|
||||
Rails.logger.info("Auth0 API query for user_id: #{uid}")
|
||||
profile = get("users/#{uid}", fields: 'app_metadata')
|
||||
{ app_metadata: {} }.merge(profile)[:app_metadata]
|
||||
private
|
||||
|
||||
def get(path, params = false)
|
||||
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
|
||||
|
||||
private
|
||||
|
||||
def get(path, params = false)
|
||||
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
|
||||
if res.code == '200'
|
||||
MultiJson.load(res.body, symbolize_keys: true)
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def access_token
|
||||
api_creds = ::PluginStore.get('mozilla-iam', 'api_creds')
|
||||
if api_creds.nil? || api_creds[:exp] < Time.now.to_i + 60
|
||||
refresh_token
|
||||
else
|
||||
api_creds[:access_token]
|
||||
end
|
||||
def access_token
|
||||
api_token = ::PluginStore.get('mozilla-iam', "#{@aud}_token")
|
||||
if api_token.nil? || api_token[:exp] < Time.now.to_i + 60
|
||||
refresh_token
|
||||
else
|
||||
api_token[:access_token]
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_token
|
||||
token = fetch_token
|
||||
payload = verify_token(token)
|
||||
::PluginStore.set('mozilla-iam', 'api_creds', { access_token: token, exp: payload['exp'] })
|
||||
token
|
||||
end
|
||||
def refresh_token
|
||||
token = fetch_token
|
||||
payload = verify_token(token)
|
||||
::PluginStore.set('mozilla-iam', "#{@aud}_token", { access_token: token, exp: payload['exp'] })
|
||||
token
|
||||
end
|
||||
|
||||
def fetch_token
|
||||
response =
|
||||
Faraday.post(
|
||||
'https://' + SiteSetting.auth0_domain + '/oauth/token',
|
||||
{
|
||||
grant_type: 'client_credentials',
|
||||
client_id: SiteSetting.auth0_client_id,
|
||||
client_secret: SiteSetting.auth0_client_secret,
|
||||
audience: 'https://' + SiteSetting.auth0_domain + '/api/v2/'
|
||||
}
|
||||
)
|
||||
MultiJson.load(response.body)['access_token']
|
||||
end
|
||||
def fetch_token
|
||||
response =
|
||||
Faraday.post(
|
||||
@token_endpoint,
|
||||
{
|
||||
grant_type: 'client_credentials',
|
||||
client_id: @client_id,
|
||||
client_secret: @client_secret,
|
||||
audience: @aud
|
||||
}
|
||||
)
|
||||
MultiJson.load(response.body)['access_token']
|
||||
end
|
||||
|
||||
def verify_token(token)
|
||||
payload, header =
|
||||
JWT.decode(
|
||||
token,
|
||||
aud: 'https://' + SiteSetting.auth0_domain + '/api/v2/',
|
||||
sub: SiteSetting.auth0_client_id + '@clients',
|
||||
verify_sub: true
|
||||
)
|
||||
payload
|
||||
end
|
||||
def verify_token(token)
|
||||
payload, header =
|
||||
JWT.decode(
|
||||
token,
|
||||
aud: @aud,
|
||||
sub: @client_id + '@clients',
|
||||
verify_sub: true
|
||||
)
|
||||
payload
|
||||
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
|
||||
|
||||
def groups
|
||||
Array(mgmt_profile[:groups]) | Array(mgmt_profile.dig(:app_metadata, :groups))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def profile
|
||||
@profile ||= API.user(@uid)
|
||||
def mgmt_profile
|
||||
@mgmt_profile ||= ManagementAPI.new.profile(@uid)
|
||||
end
|
||||
|
||||
def last_refresh
|
||||
|
@ -47,16 +51,7 @@ module MozillaIAM
|
|||
|
||||
def update_groups
|
||||
GroupMapping.all.each do |mapping|
|
||||
if mapping.authoritative
|
||||
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
|
||||
if groups.include?(mapping.iam_group_name)
|
||||
add_to_group(mapping.group)
|
||||
else
|
||||
remove_from_group(mapping.group)
|
||||
|
|
14
plugin.rb
14
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: 0.1.1
|
||||
# version: 0.1.3
|
||||
# authors: Leo McArdle
|
||||
# url: https://github.com/mozilla/discourse-mozilla-iam
|
||||
|
||||
|
@ -22,3 +22,15 @@ auth_provider(title: 'Mozilla',
|
|||
message: 'Log In / Sign Up',
|
||||
authenticator: MozillaIAM::Authenticator.new('auth0', trusted: 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'
|
||||
|
||||
describe MozillaIAM::Profile do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:profile) { MozillaIAM::Profile.new(user, "uid") }
|
||||
|
||||
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
|
||||
user = Fabricate(:user)
|
||||
result = MozillaIAM::Profile.refresh(user)
|
||||
expect(result).to be_nil
|
||||
end
|
||||
|
@ -11,23 +21,107 @@ describe MozillaIAM::Profile do
|
|||
|
||||
context '#initialize' do
|
||||
it "should save a user's uid" do
|
||||
user = Fabricate(:user)
|
||||
uid = create_uid(user.username)
|
||||
|
||||
MozillaIAM::Profile.new(user, uid)
|
||||
|
||||
expect(user.custom_fields['mozilla_iam_uid']).to eq(uid)
|
||||
profile
|
||||
expect(user.custom_fields['mozilla_iam_uid']).to eq("uid")
|
||||
end
|
||||
end
|
||||
|
||||
context '#refresh' do
|
||||
it "should refresh a user's profile if it hasn't been refreshed before" do
|
||||
user = Fabricate(:user)
|
||||
uid = create_uid(user.username)
|
||||
it "returns #force_refresh if #should_refresh? is true" do
|
||||
profile.expects(:should_refresh?).returns(true)
|
||||
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
|
||||
|
||||
|
@ -38,55 +132,106 @@ describe MozillaIAM::Profile do
|
|||
|
||||
before do
|
||||
MozillaIAM::GroupMapping.new(iam_group_name: 'iam_group',
|
||||
authoritative: false,
|
||||
group: group).save!
|
||||
end
|
||||
|
||||
it 'should remove a user from a mapped group' do
|
||||
profile.expects(:groups).returns([])
|
||||
group.users << user
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
it 'should add a user to a mapped group' do
|
||||
profile.expects(:groups).returns(['iam_group'])
|
||||
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
|
||||
end
|
||||
|
||||
it 'should work if groups attribute is undefined' do
|
||||
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
|
||||
end
|
||||
|
||||
it 'should work if groups attribute is an empty string' do
|
||||
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
|
||||
end
|
||||
|
||||
it 'should work if groups attribute is "None"' do
|
||||
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
|
||||
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
|
||||
|
|
|
@ -3,9 +3,7 @@ require_relative '../iam_helper'
|
|||
describe MozillaIAM::GroupMapping do
|
||||
it 'should be destroyed when associated group is destroyed' do
|
||||
group = Fabricate(:group)
|
||||
mapping = MozillaIAM::GroupMapping.new(iam_group_name: 'iam_group',
|
||||
authoritative: false,
|
||||
group: group)
|
||||
mapping = MozillaIAM::GroupMapping.new(iam_group_name: 'iam_group', group: group)
|
||||
mapping.save!
|
||||
mapping.reload
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ describe MozillaIAM do
|
|||
|
||||
before do
|
||||
MozillaIAM::GroupMapping.new(iam_group_name: 'iam_group',
|
||||
authoritative: false,
|
||||
group: group).save!
|
||||
TopicUser.change(user.id, topic.id, notification_level: TopicUser.notification_levels[:watching])
|
||||
user.custom_fields['mozilla_iam_uid'] = uid
|
||||
|
@ -24,7 +23,7 @@ describe MozillaIAM do
|
|||
end
|
||||
|
||||
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
|
||||
PostAlerter.post_created(reply)
|
||||
|
@ -52,7 +51,7 @@ describe MozillaIAM do
|
|||
end
|
||||
|
||||
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
|
||||
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)
|
||||
end
|
||||
|
||||
def create_jwt(payload, header)
|
||||
JWT.encode(payload, private_key, 'RS256', header)
|
||||
end
|
||||
|
||||
def create_id_token(user, additional_payload = {}, additional_header = {})
|
||||
payload = {
|
||||
name: user[:name],
|
||||
|
@ -48,7 +52,7 @@ module IAMHelpers
|
|||
kid: 'the_best_key'
|
||||
}.merge(additional_header)
|
||||
|
||||
JWT.encode(payload, private_key, 'RS256', header_fields)
|
||||
create_jwt(payload, header_fields)
|
||||
end
|
||||
|
||||
def create_uid(username)
|
||||
|
@ -71,13 +75,13 @@ module IAMHelpers
|
|||
authenticate_with_id_token create_id_token(user)
|
||||
end
|
||||
|
||||
def stub_oauth_token_request
|
||||
def stub_oauth_token_request(aud)
|
||||
stub_jwks_request
|
||||
|
||||
payload = {
|
||||
sub: 'the_best_client_id@clients',
|
||||
iss: 'https://auth.mozilla.auth0.com/',
|
||||
aud: 'https://auth.mozilla.auth0.com/api/v2/',
|
||||
aud: aud,
|
||||
exp: Time.now.to_i + 7.days,
|
||||
iat: Time.now.to_i
|
||||
}
|
||||
|
@ -93,10 +97,17 @@ module IAMHelpers
|
|||
.to_return(status: 200, body: body)
|
||||
end
|
||||
|
||||
def stub_api_users_request(uid, app_metadata)
|
||||
stub_oauth_token_request
|
||||
def stub_people_api_profile_request(uid, profile)
|
||||
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")
|
||||
.to_return(status: 200, body: MultiJson.dump(app_metadata: app_metadata))
|
||||
stub_request(:get, "https://uhbz4h3wa8.execute-api.us-west-2.amazonaws.com/prod/profile/#{uid}")
|
||||
.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
|
||||
|
|
Загрузка…
Ссылка в новой задаче