Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ client.sync_sso( #=> Synchronizes the SSO record
name: "Test Name",
username: "test_name",
email: "name@example.com",
external_id: "2"
external_id: "2",
custom_fields: {
field_1: 'potato'
}
)

# Private messages
Expand Down
19 changes: 1 addition & 18 deletions lib/discourse_api/api/sso.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,7 @@ module DiscourseApi
module API
module SSO
def sync_sso(params = {})
sso = DiscourseApi::SingleSignOn.new
sso.sso_secret = params[:sso_secret]
sso.name = params[:name]
sso.username = params[:username]
sso.email = params[:email]
sso.external_id = params[:external_id]
sso.suppress_welcome_message = params[:suppress_welcome_message] === true
sso.avatar_url = params[:avatar_url]
sso.profile_background_url = params[:profile_background_url]
sso.card_background_url = params[:card_background_url]
sso.bio = params[:bio]
sso.title = params[:title]
sso.avatar_force_update = params[:avatar_force_update] === true
sso.add_groups = params[:add_groups]
sso.remove_groups = params[:remove_groups]
params.keys.select { |key| key.to_s.start_with?("custom") }.each do |custom_key|
sso.custom_fields[custom_key] = params[custom_key]
end
sso = DiscourseApi::SingleSignOn.parse_hash(params)

post("/admin/users/sync_sso", sso.payload)
end
Expand Down
33 changes: 31 additions & 2 deletions lib/discourse_api/single_sign_on.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class SingleSignOn
#NONCE_EXPIRY_TIME = 10.minutes # minutes is a rails method and is causing an error. Is this needed in the api?

attr_accessor(*ACCESSORS)
attr_accessor :custom_fields
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate to not break ACCESSORS.each loops

attr_writer :sso_secret, :sso_url

def self.sso_secret
Expand All @@ -25,6 +26,36 @@ def self.sso_url
raise RuntimeError, "sso_url not implemented on class, be sure to set it on instance"
end

Copy link
Contributor Author

@vkozyrev vkozyrev May 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialization is similar to parse, except we don't need to stringify the keys

def self.parse_hash(payload)
payload
sso = new

sso.sso_secret = payload.delete(:sso_secret)
sso.sso_url = payload.delete(:sso_url)

ACCESSORS.each do |k|
val = payload[k]

val = val.to_i if FIXNUMS.include? k
if BOOLS.include? k
val = ["true", "false"].include?(val) ? val == "true" : nil
end
val = Array(val) if ARRAYS.include?(k) && !val.nil?
sso.send("#{k}=", val)
end
# Set custom_fields
sso.custom_fields = payload[:custom_fields]
# Add custom_fields (old format)
payload.each do |k, v|
if field = k[/^custom\.(.+)$/, 1]
# Maintain adding of .custom bug
sso.custom_fields["custom.#{field}"] = v
end
end

sso
end

def self.parse(payload, sso_secret = nil)
sso = new
sso.sso_secret = sso_secret if sso_secret
Expand Down Expand Up @@ -107,7 +138,5 @@ def unsigned_payload

Rack::Utils.build_query(payload)
end

end

end
44 changes: 43 additions & 1 deletion spec/discourse_api/api/sso_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,51 @@
describe DiscourseApi::API::SSO do
subject { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }

let(:params) do
{
sso_secret: 'abc',
sso_url: 'www.google.com',
name: 'Some User',
username: 'some_user',
email: 'some@email.com',
external_id: 'abc',
suppress_welcome_message: false,
avatar_url: 'https://www.website.com',
title: 'ruby',
avatar_force_update: false,
add_groups: ['a', 'b'],
remove_groups: ['c', 'd'],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

old + new custom_fields formats

# old format (which results in custom.custom.field_1 in unsigned_payload)
'custom.field_1' => 'tomato',
# new format
custom_fields: {
field_2: 'potato'
}
}
end
let(:expected_unsigned_payload) do
'name=Some+User&username=some_user&email=some%40email.com&'\
'avatar_url=https%3A%2F%2Fwww.website.com&external_id=abc&title=ruby'\
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note we maintain the custom.custom bug for existing users, new implementations should include custom_fields as a hash

'&add_groups=a&add_groups=b&remove_groups=c&remove_groups=d&custom.field_2=potato&'\
'custom.custom.field_1=tomato'
end
let(:sso_double) { DiscourseApi::SingleSignOn.parse_hash(params) }

describe "#sync_sso" do
before do
stub_post(/.*sync_sso.*/).to_return(body: fixture("user.json"), headers: { content_type: "application/json" })
stub_post(/.*sync_sso.*/).to_return(
body: fixture("user.json"),
headers: { content_type: "application/json" }
)
end

it 'assigns params to sso instance' do
allow(DiscourseApi::SingleSignOn).to(receive(:parse_hash).with(params).and_return(sso_double))

subject.sync_sso(params)

expect(sso_double.custom_fields).to eql({ 'custom.field_1' => 'tomato', :field_2 => 'potato' })
expect(sso_double.unsigned_payload).to eql(expected_unsigned_payload)
end

it "requests the correct resource" do
Expand Down