Commit c3738de
Changed files (43)
spec
models
requests
scim
routing
system
spec/factories/client.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :client do
uuid { SecureRandom.uuid }
spec/factories/scim.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :scim_user, class: SCIM::User do
schemas { ["urn:ietf:params:scim:schemas:core:2.0:User"] }
spec/factories/token.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :token do
uuid { SecureRandom.uuid }
spec/factories/user.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user do
email { FFaker::Internet.email }
spec/factories/user_session.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user_session do
user
spec/models/scim/user_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe SCIM::User do
@@ -51,55 +53,58 @@ RSpec.describe SCIM::User do
describe "#save!" do
context "when the user is new" do
let(:current_user) { create(:user) }
+
before { allow(Current).to receive(:user).and_return(current_user) }
context "when creating a user" do
subject { build(:scim_user) }
- before { @result = subject.save! }
-
- specify { expect(@result).to be_persisted }
- specify { expect(@result.uuid).to be_present }
- specify { expect(@result.email).to eql(subject.userName) }
- specify { expect(@result.locale).to eql(subject.locale) }
- specify { expect(@result.timezone).to eql(subject.timezone) }
- specify { expect(@result.password_digest).to be_present }
+ specify { expect(subject.save!).to be_persisted }
+ specify { expect(subject.save!.uuid).to be_present }
+ specify { expect(subject.save!.email).to eql(subject.userName) }
+ specify { expect(subject.save!.locale).to eql(subject.locale) }
+ specify { expect(subject.save!.timezone).to eql(subject.timezone) }
+ specify { expect(subject.save!.password_digest).to be_present }
end
end
context "when one user is updating another user" do
subject { build(:scim_user, id: other_user.to_param) }
+
let(:current_user) { create(:user) }
let(:other_user) { create(:user) }
before { allow(Current).to receive(:user).and_return(current_user) }
- before { @result = subject.save! }
- specify { expect(@result.uuid).to eql(other_user.uuid) }
- specify { expect(@result.email).to eql(subject.userName) }
- specify { expect(@result.locale).to eql(subject.locale) }
- specify { expect(@result.timezone).to eql(subject.timezone) }
+ specify { expect(subject.save!.uuid).to eql(other_user.uuid) }
+ specify { expect(subject.save!.email).to eql(subject.userName) }
+ specify { expect(subject.save!.locale).to eql(subject.locale) }
+ specify { expect(subject.save!.timezone).to eql(subject.timezone) }
end
context "when one user attempts to change the password of another user" do
subject { build(:scim_user, id: other_user.to_param, password: generate(:password)) }
+
let(:current_user) { create(:user) }
let(:other_user) { create(:user) }
before { allow(Current).to receive(:user).and_return(current_user) }
+
specify { expect { subject.save! }.to raise_error(StandardError) }
end
context "when a user changes their own password" do
subject { build(:scim_user, id: current_user.to_param, password: password) }
+
let!(:current_user) { create(:user) }
let(:password) { generate(:password) }
- before { freeze_time }
- before { allow(Current).to receive(:user).and_return(current_user) }
- before { @result = subject.save! }
+ before do
+ freeze_time
+ allow(Current).to receive(:user).and_return(current_user)
+ end
- specify { expect(@result.authenticate(password)).to be_truthy }
+ specify { expect(subject.save!.authenticate(password)).to be_truthy }
end
end
spec/models/client_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe Client do
@@ -13,6 +15,7 @@ RSpec.describe Client do
describe "#redirect_url" do
subject { build(:client) }
+
let(:code) { SecureRandom.uuid }
let(:redirect_uri) { subject.redirect_uri }
spec/models/token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe Token, type: :model do
@@ -5,8 +7,10 @@ RSpec.describe Token, type: :model do
subject { create(:access_token) }
context "when the token has not been revoked yet" do
- before { freeze_time }
- before { subject.revoke! }
+ before do
+ freeze_time
+ subject.revoke!
+ end
specify { expect(subject.reload.revoked_at.to_i).to eql(DateTime.now.to_i) }
end
@@ -28,6 +32,7 @@ RSpec.describe Token, type: :model do
describe ".claims_for" do
subject { described_class }
+
let(:access_token) { build_stubbed(:access_token).to_jwt }
let(:refresh_token) { build_stubbed(:refresh_token).to_jwt }
spec/models/user_session_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe UserSession do
@@ -11,18 +13,22 @@ RSpec.describe UserSession do
describe "#access" do
subject { create(:user_session) }
+
let!(:original_key) { subject.key }
- let(:request) { double(ip: "192.168.1.1", user_agent: "blah") }
+ let(:request) { instance_double(ActionDispatch::Request, ip: "192.168.1.1", user_agent: "blah") }
+ let(:result) { subject.access(request) }
- before { freeze_time }
- before { @result = subject.access(request) }
+ before do
+ freeze_time
+ result
+ end
specify { expect(subject.accessed_at).to eql(Time.now) }
specify { expect(subject.ip).to eql(request.ip) }
specify { expect(subject.user_agent).to eql(request.user_agent) }
specify { expect(subject).to be_persisted }
specify { expect(subject.key).not_to eql(original_key) }
- specify { expect(@result).to eql(subject.key) }
+ specify { expect(result).to eql(subject.key) }
end
describe ".active" do
@@ -31,11 +37,11 @@ RSpec.describe UserSession do
let!(:expired_session) { create(:user_session, :absolute_timeout_expired) }
let!(:revoked_session) { create(:user_session, :revoked) }
- specify { expect(UserSession.active).to match_array([active_session]) }
- specify { expect(UserSession.revoked).to match_array([revoked_session]) }
- specify { expect(UserSession.expired).to match_array([inactive_session, expired_session]) }
- specify { expect(UserSession.idle_timeout).to match_array([inactive_session]) }
- specify { expect(UserSession.absolute_timeout).to match_array([expired_session]) }
+ specify { expect(described_class.active).to match_array([active_session]) }
+ specify { expect(described_class.revoked).to match_array([revoked_session]) }
+ specify { expect(described_class.expired).to match_array([inactive_session, expired_session]) }
+ specify { expect(described_class.idle_timeout).to match_array([inactive_session]) }
+ specify { expect(described_class.absolute_timeout).to match_array([expired_session]) }
end
describe ".authenticate" do
@@ -44,13 +50,13 @@ RSpec.describe UserSession do
let!(:expired_session) { create(:user_session, :absolute_timeout_expired) }
let!(:revoked_session) { create(:user_session, :revoked) }
- specify { expect(UserSession.authenticate(active_session.key)).to eql(active_session) }
- specify { expect(UserSession.authenticate("blah")).to be_nil }
- specify { expect(UserSession.authenticate(inactive_session.key)).to be_nil }
- specify { expect(UserSession.authenticate(expired_session.key)).to be_nil }
- specify { expect(UserSession.authenticate(revoked_session.key)).to be_nil }
- specify { expect(UserSession.authenticate(nil)).to be_nil }
- specify { expect(UserSession.authenticate("")).to be_nil }
+ specify { expect(described_class.authenticate(active_session.key)).to eql(active_session) }
+ specify { expect(described_class.authenticate("blah")).to be_nil }
+ specify { expect(described_class.authenticate(inactive_session.key)).to be_nil }
+ specify { expect(described_class.authenticate(expired_session.key)).to be_nil }
+ specify { expect(described_class.authenticate(revoked_session.key)).to be_nil }
+ specify { expect(described_class.authenticate(nil)).to be_nil }
+ specify { expect(described_class.authenticate("")).to be_nil }
end
describe ".sudo?" do
spec/models/user_spec.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe User do
describe "#sessions" do
subject { create(:user) }
+
let!(:user_session) { create(:user_session, user: subject) }
specify { expect(subject.sessions).to match_array([user_session]) }
spec/requests/my/clients_spec.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe '/my/clients' do
context "when logged in" do
let(:current_user) { create(:user) }
+
before { http_login(current_user) }
describe "GET /my/clients" do
@@ -27,7 +30,7 @@ RSpec.describe '/my/clients' do
specify { expect(response).to redirect_to(my_clients_path) }
specify { expect(flash[:notice]).to include('success') }
- specify { expect(Client.count).to eql(1) }
+ specify { expect(Client.count).to be(1) }
end
end
end
spec/requests/my/dashboard_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe "/my/dashboard" do
spec/requests/my/mfas_spec.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe '/my/mfa' do
context "when logged in" do
let(:current_user) { create(:user) }
+
before { http_login(current_user) }
describe "GET /my/mfa" do
@@ -10,11 +13,13 @@ RSpec.describe '/my/mfa' do
let(:current_user) { create(:user, :mfa_configured) }
before { get '/my/mfa' }
+
specify { expect(response).to redirect_to(edit_my_mfa_path) }
end
context "when MFA is not set up" do
before { get '/my/mfa' }
+
specify { expect(response).to redirect_to(new_my_mfa_path) }
end
end
@@ -29,6 +34,7 @@ RSpec.describe '/my/mfa' do
context "when mfa has been set up" do
let(:current_user) { create(:user, :mfa_configured) }
+
before { get '/my/mfa/new' }
specify { expect(response).to redirect_to(edit_my_mfa_path) }
@@ -38,6 +44,7 @@ RSpec.describe '/my/mfa' do
describe "POST /my/mfa" do
context "when the secret is valid" do
let(:secret) { SecureRandom.hex(20) }
+
before { post '/my/mfa', params: { user: { mfa_secret: secret } } }
specify { expect(current_user.reload.mfa_secret).to eql(secret) }
spec/requests/my/sessions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe "/my/sessions" do
spec/requests/scim/v2/bulk_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe "/api/scim/v2/Bulk" do
@@ -13,6 +15,7 @@ RSpec.describe "/api/scim/v2/Bulk" do
describe "POST /scim/v2/Bulk" do
before { post '/scim/v2/Bulk' }
+
specify { expect(response).to have_http_status(:not_implemented) }
end
end
spec/requests/scim/v2/groups_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe "/scim/v2/groups" do
@@ -15,10 +17,12 @@ describe "/scim/v2/groups" do
describe "GET /scim/v2/groups" do
before { get '/scim/v2/groups', headers: headers }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
+
specify { expect(response).to have_http_status(:ok) }
specify { expect(response.headers['Content-Type']).to eql('application/scim+json') }
specify { expect(response.body).to be_present }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
+
specify { expect(json[:schemas]).to match_array([Scim::Shady::Messages::LIST_RESPONSE]) }
specify { expect(json[:totalResults]).to be_kind_of(Numeric) }
specify { expect(json[:Resources]).to match_array([id: user.uuid, userName: user.email]) }
@@ -33,6 +37,7 @@ describe "/scim/v2/groups" do
'Content-Type' => 'application/scim+json',
}
end
+
before { get '/scim/v2/groups', headers: bad_headers }
specify { expect(response).to have_http_status(:unauthorized) }
spec/requests/scim/v2/me_spec.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe '/scim/v2/Me' do
describe "GET /scim/v2/Me" do
before { get '/scim/v2/Me' }
+
specify { expect(response).to have_http_status(:not_implemented) }
end
end
spec/requests/scim/v2/resource_types_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe "/scim/v2/ResourceTypes" do
@@ -13,10 +15,11 @@ RSpec.describe "/scim/v2/ResourceTypes" do
describe "GET /scim/v2/ResourceTypes" do
before { get "/scim/v2/ResourceTypes", headers: headers }
+
let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(response).to have_http_status(:ok) }
- specify { expect(json.count).to eql(2) }
+ specify { expect(json.count).to be(2) }
specify { expect(json[0][:schemas]).to match_array(["urn:ietf:params:scim:schemas:core:2.0:ResourceType"]) }
specify { expect(json[0][:id]).to eql('User') }
specify { expect(json[0][:name]).to eql('User') }
@@ -36,6 +39,7 @@ RSpec.describe "/scim/v2/ResourceTypes" do
describe "GET /scim/v2/ResourceTypes/User" do
before { get "/scim/v2/ResourceTypes/User", headers: headers }
+
let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(response).to have_http_status(:ok) }
@@ -49,6 +53,7 @@ RSpec.describe "/scim/v2/ResourceTypes" do
describe "GET /scim/v2/ResourceTypes/Group" do
before { get "/scim/v2/ResourceTypes/Group", headers: headers }
+
let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(response).to have_http_status(:ok) }
spec/requests/scim/v2/schemas_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe "/scim/v2/Schemas" do
@@ -12,34 +14,31 @@ RSpec.describe "/scim/v2/Schemas" do
end
describe "GET scim/v2/Schemas" do
- before :each do
- get "/scim/v2/schemas", headers: headers
- @json = JSON.parse(response.body, symbolize_names: true)
- end
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
+
+ before { get "/scim/v2/schemas", headers: headers }
specify { expect(response).to have_http_status(:ok) }
- specify { expect(@json.count).to eql(2) }
- specify { expect(@json[0][:id]).to eql('urn:ietf:params:scim:schemas:core:2.0:User') }
- specify { expect(@json[1][:id]).to eql('urn:ietf:params:scim:schemas:core:2.0:Group') }
+ specify { expect(json.count).to be(2) }
+ specify { expect(json[0][:id]).to eql('urn:ietf:params:scim:schemas:core:2.0:User') }
+ specify { expect(json[1][:id]).to eql('urn:ietf:params:scim:schemas:core:2.0:Group') }
end
describe "GET /Schemas/urn:ietf:params:scim:schemas:core:2.0:User" do
- before :each do
- get "/scim/v2/schemas/urn:ietf:params:scim:schemas:core:2.0:User", headers: headers
- end
let(:json) { JSON.parse(response.body, symbolize_names: true) }
+ before { get "/scim/v2/schemas/urn:ietf:params:scim:schemas:core:2.0:User", headers: headers }
+
specify { expect(response).to have_http_status(:ok) }
specify { expect(json[:id]).to eql('urn:ietf:params:scim:schemas:core:2.0:User') }
specify { expect(json[:meta][:location]).to eql(scim_v2_schema_url(id: 'urn:ietf:params:scim:schemas:core:2.0:User')) }
end
describe "GET /Schemas/urn:ietf:params:scim:schemas:core:2.0:Group" do
- before :each do
- get "/scim/v2/schemas/urn:ietf:params:scim:schemas:core:2.0:Group", headers: headers
- end
let(:json) { JSON.parse(response.body, symbolize_names: true) }
+ before { get "/scim/v2/schemas/urn:ietf:params:scim:schemas:core:2.0:Group", headers: headers }
+
specify { expect(response).to have_http_status(:ok) }
specify { expect(json[:id]).to eql('urn:ietf:params:scim:schemas:core:2.0:Group') }
specify { expect(json[:meta][:location]).to eql(scim_v2_schema_url(id: 'urn:ietf:params:scim:schemas:core:2.0:Group')) }
spec/requests/scim/v2/search_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe '/scim/v1/.search' do
@@ -12,26 +14,26 @@ describe '/scim/v1/.search' do
end
describe "POST /scim/v2/.search" do
- it 'returns an empty set of results' do
- body = {
+ let(:request_body) do
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
+ {
"schemas": [Scim::Shady::Messages::SEARCH_REQUEST],
- "attributes": ["displayName", "userName"],
+ "attributes": %w[displayName userName],
"filter": "displayName sw \"smith\"",
"startIndex": 1,
"count": 10
}
- post "/scim/v2/.search", headers: headers, params: body.to_json
+ end
- expect(response).to have_http_status(:ok)
- expect(response.headers['Content-Type']).to eql('application/scim+json')
- expect(response.body).to be_present
+ before { post "/scim/v2/.search", headers: headers, params: request_body.to_json }
- json = JSON.parse(response.body, symbolize_names: true)
- expect(json[:schemas]).to match_array([Scim::Shady::Messages::LIST_RESPONSE])
- expect(json[:totalResults]).to be_zero
- expect(json[:itemsPerPage]).to be_zero
- expect(json[:startIndex]).to eql(1)
- expect(json[:Resources]).to be_empty
- end
+ specify { expect(response).to have_http_status(:ok) }
+ specify { expect(response.headers['Content-Type']).to eql('application/scim+json') }
+ specify { expect(response.body).to be_present }
+ specify { expect(json[:schemas]).to match_array([Scim::Shady::Messages::LIST_RESPONSE]) }
+ specify { expect(json[:totalResults]).to be_zero }
+ specify { expect(json[:itemsPerPage]).to be_zero }
+ specify { expect(json[:startIndex]).to be(1) }
+ specify { expect(json[:Resources]).to be_empty }
end
end
spec/requests/scim/v2/service_provider_config_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe "/ServiceProviderConfig" do
@@ -11,33 +13,26 @@ describe "/ServiceProviderConfig" do
}
end
- it 'returns a 200' do
- get '/scim/v2/ServiceProviderConfig', headers: headers
+ context "when loading the service provider configuration" do
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
- expect(response).to have_http_status(:ok)
- expect(response.body).to be_present
+ before { get '/scim/v2/ServiceProviderConfig', headers: headers }
- json = JSON.parse(response.body, symbolize_names: true)
- expect(json[:schemas]).to match_array([Scim::Shady::Schemas::SERVICE_PROVIDER_CONFIG])
- expect(json[:documentationUri]).to be_blank
- expect(json[:patch][:supported]).to be(false)
- expect(json[:bulk][:supported]).to be(false)
- expect(json[:filter][:supported]).to be(false)
- expect(json[:changePassword][:supported]).to be(false)
- expect(json[:sort][:supported]).to be(false)
- expect(json[:etag][:supported]).to be(false)
- expect(json[:authenticationSchemes]).to match_array([
- name: 'OAuth Bearer Token',
- description: 'Authentication scheme using the OAuth Bearer Token Standard',
- specUri: 'http://www.rfc-editor.org/info/rfc6750',
- documentationUri: 'http://example.com/help/oauth.html',
- type: 'oauthbearertoken',
- primary: true,
- ])
- expect(json[:meta][:location]).to eql(scim_v2_ServiceProviderConfig_url)
- expect(json[:meta][:resourceType]).to eql('ServiceProviderConfig')
- expect(json[:meta][:created]).to be_present
- expect(json[:meta][:lastModified]).to be_present
- expect(json[:meta][:version]).to be_present
+ specify { expect(response).to have_http_status(:ok) }
+ specify { expect(response.body).to be_present }
+ specify { expect(json[:schemas]).to match_array([Scim::Shady::Schemas::SERVICE_PROVIDER_CONFIG]) }
+ specify { expect(json[:documentationUri]).to be_blank }
+ specify { expect(json[:patch][:supported]).to be(false) }
+ specify { expect(json[:bulk][:supported]).to be(false) }
+ specify { expect(json[:filter][:supported]).to be(false) }
+ specify { expect(json[:changePassword][:supported]).to be(false) }
+ specify { expect(json[:sort][:supported]).to be(false) }
+ specify { expect(json[:etag][:supported]).to be(false) }
+ specify { expect(json[:authenticationSchemes]).to match_array([name: 'OAuth Bearer Token', description: 'Authentication scheme using the OAuth Bearer Token Standard', specUri: 'http://www.rfc-editor.org/info/rfc6750', documentationUri: 'http://example.com/help/oauth.html', type: 'oauthbearertoken', primary: true]) }
+ specify { expect(json[:meta][:location]).to eql(scim_v2_ServiceProviderConfig_url) }
+ specify { expect(json[:meta][:resourceType]).to eql('ServiceProviderConfig') }
+ specify { expect(json[:meta][:created]).to be_present }
+ specify { expect(json[:meta][:lastModified]).to be_present }
+ specify { expect(json[:meta][:version]).to be_present }
end
end
spec/requests/scim/v2/users_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe '/scim/v2/users' do
@@ -17,6 +19,7 @@ describe '/scim/v2/users' do
let(:locale) { 'en' }
let(:timezone) { 'Etc/UTC' }
let(:body) { { schemas: [Scim::Shady::Schemas::USER], userName: email, locale: locale, timezone: timezone } }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
before { post '/scim/v2/users', params: body.to_json, headers: headers }
@@ -24,7 +27,6 @@ describe '/scim/v2/users' do
specify { expect(response.headers['Content-Type']).to eql('application/scim+json') }
specify { expect(response.headers['Location']).to be_present }
specify { expect(response.body).to be_present }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:schemas]).to match_array([Scim::Shady::Schemas::USER]) }
specify { expect(json[:id]).to be_present }
specify { expect(json[:userName]).to eql(email) }
@@ -40,11 +42,11 @@ describe '/scim/v2/users' do
context "when a duplicate email is specified" do
let(:other_user) { create(:user) }
let(:request_body) { attributes_for(:scim_user, userName: other_user.email) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
before { post '/scim/v2/users', params: request_body.to_json, headers: headers }
specify { expect(response).to have_http_status(:bad_request) }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:schemas]).to match_array(['urn:ietf:params:scim:api:messages:2.0:Error']) }
specify { expect(json[:scimType]).to eql('uniqueness') }
specify { expect(json[:detail]).to be_instance_of(String) }
@@ -58,13 +60,14 @@ describe '/scim/v2/users' do
context "when the resource is available" do
before { get "/scim/v2/users/#{user.uuid}", headers: headers }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
+
specify { expect(response).to have_http_status(:ok) }
specify { expect(response.headers['Content-Type']).to eql('application/scim+json') }
specify { expect(response.headers['Location']).to eql(scim_v2_user_url(user)) }
specify { expect(response.headers['ETag']).to be_present }
specify { expect(response.body).to be_present }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:schemas]).to match_array([Scim::Shady::Schemas::USER]) }
specify { expect(json[:id]).to eql(user.uuid) }
specify { expect(json[:userName]).to eql(user.email) }
@@ -79,10 +82,11 @@ describe '/scim/v2/users' do
end
context "when the resource does not exist" do
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
+
before { get "/scim/v2/users/#{SecureRandom.uuid}", headers: headers }
specify { expect(response).to have_http_status(:not_found) }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:schemas]).to match_array(['urn:ietf:params:scim:api:messages:2.0:Error']) }
specify { expect(json[:detail]).to be_present }
specify { expect(json[:status]).to eql('404') }
@@ -104,12 +108,13 @@ describe '/scim/v2/users' do
end
describe "GET /scim/v2/users" do
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
+
before { get "/scim/v2/users?attributes=userName", headers: headers }
specify { expect(response).to have_http_status(:ok) }
specify { expect(response.headers['Content-Type']).to eql('application/scim+json') }
specify { expect(response.body).to be_present }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:schemas]).to match_array([Scim::Shady::Messages::LIST_RESPONSE]) }
specify { expect(json[:totalResults]).to be_zero }
specify { expect(json[:Resources]).to be_empty }
@@ -121,6 +126,7 @@ describe '/scim/v2/users' do
let(:locale) { 'ja' }
let(:timezone) { 'America/Denver' }
let(:body) { { schemas: [Scim::Shady::Schemas::USER], userName: new_email, locale: locale, timezone: timezone } }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
before { put "/scim/v2/users/#{user.uuid}", headers: headers, params: body.to_json }
@@ -128,7 +134,6 @@ describe '/scim/v2/users' do
specify { expect(response.headers['Content-Type']).to eql('application/scim+json') }
specify { expect(response.headers['Location']).to eql(scim_v2_user_url(user)) }
specify { expect(response.body).to be_present }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:schemas]).to match_array([Scim::Shady::Schemas::USER]) }
specify { expect(json[:id]).to be_present }
specify { expect(json[:userName]).to eql(new_email) }
@@ -145,17 +150,14 @@ describe '/scim/v2/users' do
describe "DELETE /scim/v2/users/:id" do
let(:other_user) { create(:user) }
- it 'deletes the user' do
- delete "/scim/v2/users/#{other_user.uuid}", headers: headers
- expect(response).to have_http_status(:no_content)
-
- get "/scim/v2/users/#{other_user.uuid}", headers: headers
- expect(response).to have_http_status(:not_found)
- expect(response.body).to be_present
- json = JSON.parse(response.body, symbolize_names: true)
- expect(json[:schemas]).to match_array([Scim::Shady::Messages::ERROR])
- expect(json[:detail]).to eql("Resource #{other_user.uuid} not found")
- expect(json[:status]).to eql("404")
+ context "when the user can be deleted" do
+ before { delete "/scim/v2/users/#{other_user.uuid}", headers: headers }
+
+ specify { expect(response).to have_http_status(:no_content) }
+ specify do
+ get "/scim/v2/users/#{other_user.uuid}", headers: headers
+ expect(response).to have_http_status(:not_found)
+ end
end
end
end
spec/requests/metadata_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe '/metadata' do
@@ -6,7 +8,7 @@ describe '/metadata' do
specify { expect(Saml::Kit::Metadata.from(response.body)).to be_valid }
specify { expect(Saml::Kit::Metadata.from(response.body).entity_id).to eql(Saml::Kit.configuration.entity_id) }
- specify { expect(response).to have_http_status(:ok) }
+ specify { expect(response).to have_http_status(:ok) }
specify { expect(response.content_type).to eq("application/samlmetadata+xml") }
end
end
spec/requests/mfas_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe "/mfa" do
@@ -15,6 +17,7 @@ RSpec.describe "/mfa" do
describe "POST /mfa" do
context "when the code is correct" do
let(:correct_code) { current_user.mfa.current_totp }
+
before { post '/mfa', params: { mfa: { code: correct_code } } }
specify { expect(response).to redirect_to(response_path) }
@@ -23,6 +26,7 @@ RSpec.describe "/mfa" do
context "when the code is incorrect" do
let(:incorrect_code) { rand(1_000) }
+
before { post '/mfa', params: { mfa: { code: incorrect_code } } }
specify { expect(response).to redirect_to(new_mfa_path) }
spec/requests/oauth_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe '/oauth' do
@@ -7,19 +9,21 @@ RSpec.describe '/oauth' do
before { http_login(current_user) }
describe "GET /oauth" do
- let(:state) { SecureRandom.uuid }
+ let(:state) { SecureRandom.uuid }
context "when the client id is known" do
let(:client) { create(:client) }
context "when requesting an authorization code" do
before { get "/oauth", params: { client_id: client.to_param, response_type: 'code', state: state, redirect_uri: client.redirect_uri } }
+
specify { expect(response).to have_http_status(:ok) }
specify { expect(response.body).to include(CGI.escapeHTML(client.name)) }
end
context "when requesting an access token" do
before { get "/oauth", params: { client_id: client.to_param, response_type: 'token', state: state, redirect_uri: client.redirect_uri } }
+
specify { expect(response).to have_http_status(:ok) }
specify { expect(response.body).to include(CGI.escapeHTML(client.name)) }
end
@@ -39,10 +43,11 @@ RSpec.describe '/oauth' do
end
describe "GET /oauth/authorize" do
- let(:state) { SecureRandom.uuid }
+ let(:state) { SecureRandom.uuid }
context "when the client id is known" do
let(:client) { create(:client) }
+
before { get "/oauth/authorize", params: { client_id: client.to_param, response_type: 'code', state: state, redirect_uri: client.redirect_uri } }
specify { expect(response).to have_http_status(:ok) }
@@ -56,7 +61,7 @@ RSpec.describe '/oauth' do
let(:state) { SecureRandom.uuid }
context "when the client requested an authorization code" do
- before :each do
+ before do
get "/oauth", params: { client_id: client.to_param, response_type: 'code', state: state, redirect_uri: client.redirect_uri }
post "/oauth"
end
@@ -68,7 +73,7 @@ RSpec.describe '/oauth' do
let(:token) { Token.access.active.last&.to_jwt }
let(:scope) { "admin" }
- before :each do
+ before do
get "/oauth", params: { client_id: client.to_param, response_type: 'token', state: state, redirect_uri: client.redirect_uri }
post "/oauth"
end
@@ -81,7 +86,7 @@ RSpec.describe '/oauth' do
let(:code_verifier) { SecureRandom.hex(128) }
let(:code_challenge) { Base64.urlsafe_encode64(Digest::SHA256.hexdigest(code_verifier)) }
- before :each do
+ before do
get "/oauth", params: {
client_id: client.to_param,
response_type: 'code',
@@ -102,7 +107,7 @@ RSpec.describe '/oauth' do
let(:token) { Token.access.active.last&.to_jwt }
let(:code_verifier) { SecureRandom.hex(128) }
- before :each do
+ before do
get "/oauth", params: {
client_id: client.to_param,
response_type: 'code',
@@ -123,7 +128,7 @@ RSpec.describe '/oauth' do
let(:token) { Token.access.active.last&.to_jwt }
let(:code_verifier) { SecureRandom.hex(128) }
- before :each do
+ before do
get "/oauth", params: {
client_id: client.to_param,
response_type: 'code',
@@ -148,7 +153,7 @@ RSpec.describe '/oauth' do
context "when the state parameter looks malicious" do
let(:state) { "<script>alert('hi');</script>" }
- before :each do
+ before do
get "/oauth", params: { client_id: client.to_param, response_type: 'token', state: state, redirect_uri: client.redirect_uri }
post "/oauth"
end
spec/requests/registrations_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe '/registrations' do
@@ -11,10 +13,11 @@ RSpec.describe '/registrations' do
describe "POST /registrations" do
context "when the new registration data is valid" do
let(:email) { FFaker::Internet.email }
+
before { post "/registrations", params: { user: { email: email, password: "password" } } }
specify { expect(response).to redirect_to(new_session_url) }
- specify { expect(User.count).to eql(1) }
+ specify { expect(User.count).to be(1) }
specify { expect(User.last.email).to eql(email) }
end
spec/requests/response_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe "/response" do
@@ -25,7 +27,7 @@ RSpec.describe "/response" do
end
end
- before :each do
+ before do
Saml::Kit.configuration.registry = registry
allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
get redirect_binding.serialize(Saml::Kit::AuthenticationRequest.builder, relay_state: relay_state)[0]
@@ -42,8 +44,10 @@ RSpec.describe "/response" do
end
context "when the SAML request is no longer valid" do
- before { allow_any_instance_of(Saml::Kit::AuthenticationRequest).to receive(:valid?).and_return(false) }
- before { get '/response' }
+ before do
+ allow_any_instance_of(Saml::Kit::AuthenticationRequest).to receive(:valid?).and_return(false)
+ get '/response'
+ end
specify { expect(response).to have_http_status(:forbidden) }
end
spec/requests/sessions_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'rails_helper'
-describe SessionsController do
+describe "/sessions" do
let(:registry) { Saml::Kit::DefaultRegistry.new }
let(:issuer) { Saml::Kit.configuration.entity_id }
let(:sp_metadata) do
@@ -15,6 +17,7 @@ describe SessionsController do
def session_id_from(response)
cookies = response.headers['Set-Cookie']
return if cookies.nil?
+
cookies.split("\;")[0].split("=")[1]
end
@@ -23,11 +26,12 @@ describe SessionsController do
context "when the user is already logged in" do
let(:user) { create(:user) }
+
before { http_login(user) }
context "when a registered SAML request is provided" do
- before { allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata) }
- before :each do
+ before do
+ allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
url, saml_params = post_binding.serialize(Saml::Kit::AuthenticationRequest.builder)
post url, params: saml_params
follow_redirect!
@@ -38,7 +42,7 @@ describe SessionsController do
end
context "when an unregistered SAML request is provided" do
- before :each do
+ before do
url, saml_params = post_binding.serialize(Saml::Kit::AuthenticationRequest.builder)
post url, params: saml_params
end
@@ -55,8 +59,8 @@ describe SessionsController do
context "when the user is not logged in" do
context "when a registered SAML request is provided" do
- before { allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata) }
- before :each do
+ before do
+ allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
url, saml_params = post_binding.serialize(Saml::Kit::AuthenticationRequest.builder)
post url, params: saml_params
end
@@ -68,7 +72,7 @@ describe SessionsController do
end
context "when an unregistered SAML request is provided" do
- before :each do
+ before do
url, saml_params = post_binding.serialize(Saml::Kit::AuthenticationRequest.builder)
post url, params: saml_params
end
@@ -92,9 +96,11 @@ describe SessionsController do
before { http_login(create(:user)) }
context "when a registered SAML request is provided" do
- before { allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata) }
- before { get redirect_binding.serialize(Saml::Kit::AuthenticationRequest.builder)[0] }
- before { follow_redirect! }
+ before do
+ allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
+ get redirect_binding.serialize(Saml::Kit::AuthenticationRequest.builder)[0]
+ follow_redirect!
+ end
specify { expect(response).to have_http_status(:ok) }
specify { expect(response.body).to include("Sending Response to Service Provider") }
@@ -115,8 +121,10 @@ describe SessionsController do
context "when the user is not logged in" do
context "when a registered SAML request is provided" do
- before { allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata) }
- before { get redirect_binding.serialize(Saml::Kit::AuthenticationRequest.builder)[0] }
+ before do
+ allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
+ get redirect_binding.serialize(Saml::Kit::AuthenticationRequest.builder)[0]
+ end
specify { expect(response).to have_http_status(:ok) }
specify { expect(session[:saml]).to be_present }
@@ -146,6 +154,7 @@ describe SessionsController do
context "when a SAMLRequest is not present" do
context "when the credentials are correct" do
before { post '/session', params: { user: { email: user.email, password: password } } }
+
specify { expect(response).to redirect_to(response_path) }
end
@@ -161,7 +170,7 @@ describe SessionsController do
let(:redirect_binding) { Saml::Kit::Bindings::HttpRedirect.new(location: new_session_url) }
let(:relay_state) { SecureRandom.uuid }
- before :each do
+ before do
allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
get redirect_binding.serialize(Saml::Kit::AuthenticationRequest.builder, relay_state: relay_state)[0]
end
@@ -188,9 +197,11 @@ describe SessionsController do
let(:user) { create(:user) }
context "when receiving a logout request" do
- before :each do
+ let(:session_id) { session_id_from(response) }
+
+ before do
http_login(user)
- @session_id = session_id_from(response)
+ session_id
allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
builder = Saml::Kit::LogoutRequest.builder(user) do |x|
@@ -206,11 +217,11 @@ describe SessionsController do
specify { expect(response.body).to include("SAMLResponse") }
specify { expect(response.body).to include(sp_metadata.single_logout_service_for(binding: :http_post).location) }
specify { expect(session_id_from(response)).to be_present }
- specify { expect(session_id_from(response)).not_to eql(@session_id) }
+ specify { expect(session_id_from(response)).not_to eql(session_id) }
end
context "when receiving a logout response" do
- before :each do
+ before do
allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
builder = Saml::Kit::LogoutResponse.builder(Saml::Kit::AuthenticationRequest.build) do |x|
x.issuer = issuer
@@ -225,14 +236,15 @@ describe SessionsController do
context "when logging out of the IDP only" do
let(:user) { create(:user) }
+ let(:session_id) { session_id_from(response) }
- before :each do
+ before do
http_login(user)
- @session_id = session_id_from(response)
+ session_id
delete session_path
end
- specify { expect(session_id_from(response)).not_to eql(@session_id) }
+ specify { expect(session_id_from(response)).not_to eql(session_id) }
specify { expect(session_id_from(response)).to be_present }
specify { expect(response).to redirect_to(new_session_path) }
end
spec/requests/tokens_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe '/tokens' do
@@ -9,6 +11,7 @@ RSpec.describe '/tokens' do
context "when using the authorization_code grant" do
context "when the code is still valid" do
let(:authorization) { create(:authorization, client: client) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
before { post '/oauth/token', params: { grant_type: 'authorization_code', code: authorization.code }, headers: headers }
@@ -16,8 +19,6 @@ RSpec.describe '/tokens' do
specify { expect(response.headers['Content-Type']).to include('application/json') }
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
-
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:access_token]).to be_present }
specify { expect(json[:token_type]).to eql('Bearer') }
specify { expect(json[:expires_in]).to eql(1.hour.to_i) }
@@ -27,6 +28,7 @@ RSpec.describe '/tokens' do
context "when the code is expired" do
let(:authorization) { create(:authorization, client: client, expired_at: 1.second.ago) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
before { post '/oauth/token', params: { grant_type: 'authorization_code', code: authorization.code }, headers: headers }
@@ -34,37 +36,36 @@ RSpec.describe '/tokens' do
specify { expect(response.headers['Content-Type']).to include('application/json') }
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
-
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:error]).to eql('invalid_request') }
end
context "when the code is not known" do
before { post '/oauth/token', params: { grant_type: 'authorization_code', code: SecureRandom.hex(20) }, headers: headers }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
+
specify { expect(response).to have_http_status(:bad_request) }
specify { expect(response.headers['Content-Type']).to include('application/json') }
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:error]).to eql('invalid_request') }
end
context "when the authorization was created with the code_challenge_method of SHA256" do
let(:code_verifier) { SecureRandom.hex(128) }
- before { post '/oauth/token', params: { grant_type: 'authorization_code', code: SecureRandom.hex(20) }, headers: headers }
-
- let(:authorization) { create(:authorization, client: client, challenge: Base64.urlsafe_encode64(Digest::SHA256.hexdigest(code_verifier)) , challenge_method: :sha256) }
+ let(:authorization) { create(:authorization, client: client, challenge: Base64.urlsafe_encode64(Digest::SHA256.hexdigest(code_verifier)), challenge_method: :sha256) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
- before { post '/oauth/token', params: { grant_type: 'authorization_code', code: authorization.code, code_verifier: code_verifier }, headers: headers }
+ before do
+ post '/oauth/token', params: { grant_type: 'authorization_code', code: SecureRandom.hex(20) }, headers: headers
+ post '/oauth/token', params: { grant_type: 'authorization_code', code: authorization.code, code_verifier: code_verifier }, headers: headers
+ end
specify { expect(response).to have_http_status(:ok) }
specify { expect(response.headers['Content-Type']).to include('application/json') }
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
-
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:access_token]).to be_present }
specify { expect(json[:token_type]).to eql('Bearer') }
specify { expect(json[:expires_in]).to eql(1.hour.to_i) }
@@ -74,18 +75,18 @@ RSpec.describe '/tokens' do
context "when the authorization was created with the code_challenge_method of plain" do
let(:code_verifier) { SecureRandom.hex(128) }
- before { post '/oauth/token', params: { grant_type: 'authorization_code', code: SecureRandom.hex(20) }, headers: headers }
-
- let(:authorization) { create(:authorization, client: client, challenge: code_verifier , challenge_method: :plain) }
+ let(:authorization) { create(:authorization, client: client, challenge: code_verifier, challenge_method: :plain) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
- before { post '/oauth/token', params: { grant_type: 'authorization_code', code: authorization.code, code_verifier: code_verifier }, headers: headers }
+ before do
+ post '/oauth/token', params: { grant_type: 'authorization_code', code: SecureRandom.hex(20) }, headers: headers
+ post '/oauth/token', params: { grant_type: 'authorization_code', code: authorization.code, code_verifier: code_verifier }, headers: headers
+ end
specify { expect(response).to have_http_status(:ok) }
specify { expect(response.headers['Content-Type']).to include('application/json') }
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
-
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:access_token]).to be_present }
specify { expect(json[:token_type]).to eql('Bearer') }
specify { expect(json[:expires_in]).to eql(1.hour.to_i) }
@@ -95,49 +96,51 @@ RSpec.describe '/tokens' do
context "when the SHA256 challenge is invalid" do
let(:code_verifier) { SecureRandom.hex(128) }
- before { post '/oauth/token', params: { grant_type: 'authorization_code', code: SecureRandom.hex(20) }, headers: headers }
-
- let(:authorization) { create(:authorization, client: client, challenge: Base64.urlsafe_encode64(Digest::SHA256.hexdigest(code_verifier)) , challenge_method: :sha256) }
+ let(:authorization) { create(:authorization, client: client, challenge: Base64.urlsafe_encode64(Digest::SHA256.hexdigest(code_verifier)), challenge_method: :sha256) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
- before { post '/oauth/token', params: { grant_type: 'authorization_code', code: authorization.code, code_verifier: 'invalid' }, headers: headers }
+ before do
+ post '/oauth/token', params: { grant_type: 'authorization_code', code: SecureRandom.hex(20) }, headers: headers
+ post '/oauth/token', params: { grant_type: 'authorization_code', code: authorization.code, code_verifier: 'invalid' }, headers: headers
+ end
specify { expect(response).to have_http_status(:bad_request) }
specify { expect(response.headers['Content-Type']).to include('application/json') }
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:error]).to eql('invalid_request') }
end
context "when the plain challenge is invalid" do
let(:code_verifier) { SecureRandom.hex(128) }
- before { post '/oauth/token', params: { grant_type: 'authorization_code', code: SecureRandom.hex(20) }, headers: headers }
-
let(:authorization) { create(:authorization, client: client, challenge: code_verifier, challenge_method: :plain) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
- before { post '/oauth/token', params: { grant_type: 'authorization_code', code: authorization.code, code_verifier: 'invalid' }, headers: headers }
+ before do
+ post '/oauth/token', params: { grant_type: 'authorization_code', code: SecureRandom.hex(20) }, headers: headers
+ post '/oauth/token', params: { grant_type: 'authorization_code', code: authorization.code, code_verifier: 'invalid' }, headers: headers
+ end
specify { expect(response).to have_http_status(:bad_request) }
specify { expect(response.headers['Content-Type']).to include('application/json') }
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:error]).to eql('invalid_request') }
end
end
context "when requesting a token using the client_credentials grant" do
context "when the client credentials are valid" do
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
+
before { post '/oauth/token', params: { grant_type: 'client_credentials' }, headers: headers }
specify { expect(response).to have_http_status(:ok) }
specify { expect(response.headers['Content-Type']).to include('application/json') }
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
-
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:access_token]).to be_present }
specify { expect(json[:token_type]).to eql('Bearer') }
specify { expect(json[:expires_in]).to eql(1.hour.to_i) }
@@ -146,10 +149,11 @@ RSpec.describe '/tokens' do
context "when the credentials are unknown" do
let(:headers) { { 'Authorization' => 'invalid' } }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
+
before { post '/oauth/token', params: { grant_type: 'client_credentials' }, headers: headers }
specify { expect(response).to have_http_status(:unauthorized) }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:error]).to eql('invalid_client') }
end
end
@@ -157,14 +161,14 @@ RSpec.describe '/tokens' do
context "when requesting tokens using the resource owner password credentials grant" do
context "when the credentials are valid" do
let(:user) { create(:user) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
+
before { post '/oauth/token', params: { grant_type: 'password', username: user.email, password: user.password }, headers: headers }
specify { expect(response).to have_http_status(:ok) }
specify { expect(response.headers['Content-Type']).to include('application/json') }
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
-
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:access_token]).to be_present }
specify { expect(json[:token_type]).to eql('Bearer') }
specify { expect(json[:expires_in]).to eql(1.hour.to_i) }
@@ -172,10 +176,11 @@ RSpec.describe '/tokens' do
end
context "when the credentials are invalid" do
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
+
before { post '/oauth/token', params: { grant_type: 'password', username: generate(:email), password: generate(:password) }, headers: headers }
specify { expect(response).to have_http_status(:bad_request) }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:error]).to eql('invalid_request') }
end
end
@@ -183,6 +188,7 @@ RSpec.describe '/tokens' do
context "when exchanging a refresh token for a new access token" do
context "when the refresh token is still active" do
let(:refresh_token) { create(:refresh_token) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
before { post '/oauth/token', params: { grant_type: 'refresh_token', refresh_token: refresh_token.to_jwt }, headers: headers }
@@ -190,8 +196,6 @@ RSpec.describe '/tokens' do
specify { expect(response.headers['Content-Type']).to include('application/json') }
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
-
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:access_token]).to be_present }
specify { expect(json[:token_type]).to eql('Bearer') }
specify { expect(json[:expires_in]).to eql(1.hour.to_i) }
@@ -203,11 +207,12 @@ RSpec.describe '/tokens' do
context "when exchanging a SAML 2.0 assertion grant for tokens" do
context "when the assertion contains a valid email address" do
let(:user) { create(:user) }
- let(:saml_request) { double(id: Xml::Kit::Id.generate, issuer: Saml::Kit.configuration.entity_id, trusted?: true) }
+ let(:saml_request) { instance_double(Saml::Kit::AuthenticationRequest, id: Xml::Kit::Id.generate, issuer: Saml::Kit.configuration.entity_id, trusted?: true) }
let(:saml) { Saml::Kit::Assertion.build_xml(user, saml_request) }
let(:metadata) { Saml::Kit::Metadata.build(&:build_identity_provider) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
- before :each do
+ before do
allow(Saml::Kit.configuration.registry).to receive(:metadata_for).and_return(metadata)
post '/oauth/token', params: {
grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer',
@@ -219,8 +224,6 @@ RSpec.describe '/tokens' do
specify { expect(response.headers['Content-Type']).to include('application/json') }
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
-
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:access_token]).to be_present }
specify { expect(json[:token_type]).to eql('Bearer') }
specify { expect(json[:expires_in]).to eql(1.hour.to_i) }
@@ -229,11 +232,12 @@ RSpec.describe '/tokens' do
context "when the assertion contains a valid uuid" do
let(:user) { create(:user) }
- let(:saml_request) { double(id: Xml::Kit::Id.generate, issuer: Saml::Kit.configuration.entity_id, trusted?: true, name_id_format: Saml::Kit::Namespaces::PERSISTENT) }
+ let(:saml_request) { instance_double(Saml::Kit::AuthenticationRequest, id: Xml::Kit::Id.generate, issuer: Saml::Kit.configuration.entity_id, trusted?: true, name_id_format: Saml::Kit::Namespaces::PERSISTENT) }
let(:saml) { Saml::Kit::Assertion.build_xml(user, saml_request) }
let(:metadata) { Saml::Kit::Metadata.build(&:build_identity_provider) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
- before :each do
+ before do
allow(Saml::Kit.configuration.registry).to receive(:metadata_for).and_return(metadata)
post '/oauth/token', params: {
grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer',
@@ -245,8 +249,6 @@ RSpec.describe '/tokens' do
specify { expect(response.headers['Content-Type']).to include('application/json') }
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
-
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:access_token]).to be_present }
specify { expect(json[:token_type]).to eql('Bearer') }
specify { expect(json[:expires_in]).to eql(1.hour.to_i) }
@@ -256,11 +258,12 @@ RSpec.describe '/tokens' do
context "when the assertion is not a valid document" do
let(:user) { create(:user) }
- let(:saml_request) { double(id: Xml::Kit::Id.generate, issuer: Saml::Kit.configuration.entity_id) }
+ let(:saml_request) { instance_double(Saml::Kit::AuthenticationRequest, id: Xml::Kit::Id.generate, issuer: Saml::Kit.configuration.entity_id) }
let(:saml) { 'invalid' }
let(:metadata) { Saml::Kit::Metadata.build(&:build_identity_provider) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
- before :each do
+ before do
allow(Saml::Kit.configuration.registry).to receive(:metadata_for).and_return(metadata)
post '/oauth/token', params: {
grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer',
@@ -272,19 +275,18 @@ RSpec.describe '/tokens' do
specify { expect(response.headers['Content-Type']).to include('application/json') }
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
-
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:error]).to eql('invalid_request') }
end
context "when the assertion has an invalid signature" do
let(:user) { create(:user) }
- let(:saml_request) { double(id: Xml::Kit::Id.generate, issuer: Saml::Kit.configuration.entity_id, trusted?: false) }
+ let(:saml_request) { instance_double(Saml::Kit::AuthenticationRequest, id: Xml::Kit::Id.generate, issuer: Saml::Kit.configuration.entity_id, trusted?: false) }
let(:key_pair) { Xml::Kit::KeyPair.generate(use: :signing) }
let(:saml) { Saml::Kit::Assertion.build_xml(user, saml_request) { |x| x.sign_with(key_pair) } }
let(:metadata) { Saml::Kit::Metadata.build(&:build_identity_provider) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
- before :each do
+ before do
allow(Saml::Kit.configuration.registry).to receive(:metadata_for).and_return(metadata)
post '/oauth/token', params: {
grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer',
@@ -297,7 +299,6 @@ RSpec.describe '/tokens' do
specify { expect(response.headers['Cache-Control']).to include('no-store') }
specify { expect(response.headers['Pragma']).to eql('no-cache') }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
specify { expect(json[:error]).to eql('invalid_request') }
end
end
@@ -305,13 +306,13 @@ RSpec.describe '/tokens' do
describe "POST /tokens/introspect" do
context "when the access_token is valid" do
let(:token) { create(:access_token) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
before { post '/tokens/introspect', params: { token: token.to_jwt }, headers: headers }
specify { expect(response).to have_http_status(:ok) }
specify { expect(response['Content-Type']).to include('application/json') }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
- specify { expect(json[:active]).to eql(true) }
+ specify { expect(json[:active]).to be(true) }
specify { expect(json[:sub]).to eql(token.claims[:sub]) }
specify { expect(json[:aud]).to eql(token.claims[:aud]) }
specify { expect(json[:iss]).to eql(token.claims[:iss]) }
@@ -321,13 +322,13 @@ RSpec.describe '/tokens' do
context "when the refresh_token is valid" do
let(:token) { create(:refresh_token) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
before { post '/tokens/introspect', params: { token: token.to_jwt }, headers: headers }
specify { expect(response).to have_http_status(:ok) }
specify { expect(response['Content-Type']).to include('application/json') }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
- specify { expect(json[:active]).to eql(true) }
+ specify { expect(json[:active]).to be(true) }
specify { expect(json[:sub]).to eql(token.claims[:sub]) }
specify { expect(json[:aud]).to eql(token.claims[:aud]) }
specify { expect(json[:iss]).to eql(token.claims[:iss]) }
@@ -337,24 +338,24 @@ RSpec.describe '/tokens' do
context "when the token is revoked" do
let(:token) { create(:access_token, :revoked) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
before { post '/tokens/introspect', params: { token: token.to_jwt }, headers: headers }
specify { expect(response).to have_http_status(:ok) }
specify { expect(response['Content-Type']).to include('application/json') }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
- specify { expect(json[:active]).to eql(false) }
+ specify { expect(json[:active]).to be(false) }
end
context "when the token is expired" do
let(:token) { create(:access_token, :expired) }
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
before { post '/tokens/introspect', params: { token: token.to_jwt }, headers: headers }
specify { expect(response).to have_http_status(:ok) }
specify { expect(response['Content-Type']).to include('application/json') }
- let(:json) { JSON.parse(response.body, symbolize_names: true) }
- specify { expect(json[:active]).to eql(false) }
+ specify { expect(json[:active]).to be(false) }
end
end
spec/routing/scim_spec.rb
@@ -1,22 +1,25 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "/scim" do
let(:id) { SecureRandom.uuid }
+
it { expect(get: "scim/v2/users/#{id}").to route_to(controller: "scim/v2/users", action: "show", id: id, format: :scim) }
it { expect(post: "scim/v2/users").to route_to(controller: "scim/v2/users", action: "create", format: :scim) }
it { expect(put: "scim/v2/users/#{id}").to route_to(controller: "scim/v2/users", action: "update", id: id, format: :scim) }
it { expect(patch: "scim/v2/users/#{id}").to route_to(controller: "scim/v2/users", action: "update", id: id, format: :scim) }
it { expect(delete: "scim/v2/users/#{id}").to route_to(controller: "scim/v2/users", action: "destroy", id: id, format: :scim) }
- #it { expect(get: "scim/v2/groups/#{id}").to route_to(controller: "scim/v2/groups", action: "show", id: id, format: :scim) }
- #it { expect(post: "scim/v2/groups").to route_to(controller: "scim/v2/groups", action: "create", format: :scim) }
- #it { expect(put: "scim/v2/groups/#{id}").to route_to(controller: "scim/v2/groups", action: "update", id: id, format: :scim) }
- #it { expect(patch: "scim/v2/groups/#{id}").to route_to(controller: "scim/v2/groups", action: "update", id: id, format: :scim) }
- #it { expect(delete: "scim/v2/groups/#{id}").to route_to(controller: "scim/v2/groups", action: "destroy", id: id, format: :scim) }
+ # it { expect(get: "scim/v2/groups/#{id}").to route_to(controller: "scim/v2/groups", action: "show", id: id, format: :scim) }
+ # it { expect(post: "scim/v2/groups").to route_to(controller: "scim/v2/groups", action: "create", format: :scim) }
+ # it { expect(put: "scim/v2/groups/#{id}").to route_to(controller: "scim/v2/groups", action: "update", id: id, format: :scim) }
+ # it { expect(patch: "scim/v2/groups/#{id}").to route_to(controller: "scim/v2/groups", action: "update", id: id, format: :scim) }
+ # it { expect(delete: "scim/v2/groups/#{id}").to route_to(controller: "scim/v2/groups", action: "destroy", id: id, format: :scim) }
- #it { expect(get: "/scim/v2/me").to route_to(controller: 'hi') }
+ # it { expect(get: "/scim/v2/me").to route_to(controller: 'hi') }
it { expect(get: "scim/v2/ServiceProviderConfig").to route_to(controller: "scim/v2/service_providers", action: "show", format: :scim) }
it { expect(get: "scim/v2/ResourceTypes").to route_to(controller: "scim/v2/resource_types", action: "index", format: :scim) }
it { expect(get: "scim/v2/schemas").to route_to(controller: "scim/v2/schemas", action: "index", format: :scim) }
- #it { expect(post: "scim/v2/bulk").to route_to(controller: "scim/v2/bulk", action: "update", format: :scim) }
+ # it { expect(post: "scim/v2/bulk").to route_to(controller: "scim/v2/bulk", action: "update", format: :scim) }
end
spec/support/active_support.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.include ActiveSupport::Testing::TimeHelpers
- config.after :each do |example|
+ config.after do |_example|
travel_back
end
config.include(Module.new do
spec/support/factory_bot.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
spec/support/request.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.include(Module.new do
def http_login(user, skip_mfa: false)
post '/session', params: { user: { email: user.email, password: user.password } }
return if skip_mfa
+
mfa_login(user) if user.mfa.setup?
end
spec/support/system.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'capybara/rails'
require 'capybara-screenshot/rspec'
spec/system/direct_login_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe "when logging in directly in to the application", js: true do
spec/factories.rb
@@ -1,4 +1,6 @@
+# frozen_string_literal: true
+
FactoryBot.define do
- sequence(:email) { |n| FFaker::Internet.email }
- sequence(:password) { |n| FFaker::Internet.password }
+ sequence(:email) { |_n| FFaker::Internet.email }
+ sequence(:password) { |_n| FFaker::Internet.password }
end
spec/rails_helper.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
-require File.expand_path('../../config/environment', __FILE__)
+require File.expand_path('../config/environment', __dir__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
spec/spec_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This file was generated by the `rails generate rspec:install` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause
@@ -44,53 +46,51 @@ RSpec.configure do |config|
# triggering implicit auto-inclusion in groups with matching metadata.
config.shared_context_metadata_behavior = :apply_to_host_groups
-# The settings below are suggested to provide a good initial experience
-# with RSpec, but feel free to customize to your heart's content.
-=begin
- # This allows you to limit a spec run to individual examples or groups
- # you care about by tagging them with `:focus` metadata. When nothing
- # is tagged with `:focus`, all examples get run. RSpec also provides
- # aliases for `it`, `describe`, and `context` that include `:focus`
- # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
- config.filter_run_when_matching :focus
-
- # Allows RSpec to persist some state between runs in order to support
- # the `--only-failures` and `--next-failure` CLI options. We recommend
- # you configure your source control system to ignore this file.
- config.example_status_persistence_file_path = "spec/examples.txt"
-
- # Limits the available syntax to the non-monkey patched syntax that is
- # recommended. For more details, see:
- # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
- # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
- # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
- config.disable_monkey_patching!
-
- # Many RSpec users commonly either run the entire suite or an individual
- # file, and it's useful to allow more verbose output when running an
- # individual spec file.
- if config.files_to_run.one?
- # Use the documentation formatter for detailed output,
- # unless a formatter has already been configured
- # (e.g. via a command-line flag).
- config.default_formatter = "doc"
- end
-
- # Print the 10 slowest examples and example groups at the
- # end of the spec run, to help surface which specs are running
- # particularly slow.
- config.profile_examples = 10
-
- # Run specs in random order to surface order dependencies. If you find an
- # order dependency and want to debug it, you can fix the order by providing
- # the seed, which is printed after each run.
- # --seed 1234
- config.order = :random
-
- # Seed global randomization in this process using the `--seed` CLI option.
- # Setting this allows you to use `--seed` to deterministically reproduce
- # test failures related to randomization by passing the same `--seed` value
- # as the one that triggered the failure.
- Kernel.srand config.seed
-=end
+ # The settings below are suggested to provide a good initial experience
+ # with RSpec, but feel free to customize to your heart's content.
+ # # This allows you to limit a spec run to individual examples or groups
+ # # you care about by tagging them with `:focus` metadata. When nothing
+ # # is tagged with `:focus`, all examples get run. RSpec also provides
+ # # aliases for `it`, `describe`, and `context` that include `:focus`
+ # # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
+ # config.filter_run_when_matching :focus
+ #
+ # # Allows RSpec to persist some state between runs in order to support
+ # # the `--only-failures` and `--next-failure` CLI options. We recommend
+ # # you configure your source control system to ignore this file.
+ # config.example_status_persistence_file_path = "spec/examples.txt"
+ #
+ # # Limits the available syntax to the non-monkey patched syntax that is
+ # # recommended. For more details, see:
+ # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
+ # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
+ # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
+ # config.disable_monkey_patching!
+ #
+ # # Many RSpec users commonly either run the entire suite or an individual
+ # # file, and it's useful to allow more verbose output when running an
+ # # individual spec file.
+ # if config.files_to_run.one?
+ # # Use the documentation formatter for detailed output,
+ # # unless a formatter has already been configured
+ # # (e.g. via a command-line flag).
+ # config.default_formatter = "doc"
+ # end
+ #
+ # # Print the 10 slowest examples and example groups at the
+ # # end of the spec run, to help surface which specs are running
+ # # particularly slow.
+ # config.profile_examples = 10
+ #
+ # # Run specs in random order to surface order dependencies. If you find an
+ # # order dependency and want to debug it, you can fix the order by providing
+ # # the seed, which is printed after each run.
+ # # --seed 1234
+ # config.order = :random
+ #
+ # # Seed global randomization in this process using the `--seed` CLI option.
+ # # Setting this allows you to use `--seed` to deterministically reproduce
+ # # test failures related to randomization by passing the same `--seed` value
+ # # as the one that triggered the failure.
+ # Kernel.srand config.seed
end
.rubocop.yml
@@ -1,3 +1,4 @@
+require: rubocop-rspec
# For a list of available cops see:
# https://github.com/bbatsov/rubocop/blob/master/config/default.yml
AllCops:
@@ -11,20 +12,45 @@ AllCops:
- 'db/seeds.rb'
- 'node_modules/**/*'
- 'pkg/**/*'
- - 'spec/**/*'
- - 'test/**/*'
- 'tmp/**/*'
- 'vendor/**/*'
Metrics/AbcSize:
Enabled: false
+Metrics/BlockLength:
+ Exclude:
+ - 'spec/**/*'
+
+Metrics/LineLength:
+ Exclude:
+ - 'spec/**/*'
+
Metrics/MethodLength:
Enabled: false
Metrics/PerceivedComplexity:
Enabled: false
+RSpec/DescribeClass:
+ Enabled: false
+
+RSpec/ExampleLength:
+ Exclude:
+ - 'spec/system/**/*'
+
+RSpec/LetSetup:
+ Enabled: false
+
+RSpec/MultipleExpectations:
+ Max: 2
+
+RSpec/NamedSubject:
+ Enabled: false
+
+RSpec/NestedGroups:
+ Max: 5
+
Style/Documentation:
Enabled: false
Gemfile
@@ -34,19 +34,22 @@ group :development do
gem 'brakeman', '~> 4.3'
gem 'bundler-audit', '~> 0.6'
gem 'listen', '>= 3.0.5', '< 3.2'
- gem 'rubocop', '~> 0.58'
+ gem 'rubocop', '~> 0.59', require: false
gem 'web-console', '>= 3.3.0'
end
group :development, :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
+ gem 'i18n-tasks', '~> 0.9.24'
+ gem 'rspec-rails', '~> 3.8'
+ gem 'sqlite3'
+end
+group :test do
gem 'capybara', '~> 3.6'
gem 'capybara-screenshot', '~> 1.0'
gem 'factory_bot_rails', '~> 4.11'
gem 'ffaker', '~> 2.10'
- gem 'i18n-tasks', '~> 0.9.24'
- gem 'rspec-rails', '~> 3.7'
+ gem 'rubocop-rspec', '~> 1.30'
gem 'selenium-webdriver', '~> 3.14'
- gem 'sqlite3'
gem 'webmock', '~> 3.4'
end
group :production do
Gemfile.lock
@@ -64,7 +64,7 @@ GEM
bundler (~> 1.2)
thor (~> 0.18)
byebug (10.0.2)
- capybara (3.8.1)
+ capybara (3.9.0)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
@@ -144,13 +144,13 @@ GEM
sass (~> 3.4)
jekyll-seo-tag (2.5.0)
jekyll (~> 3.3)
- jekyll-watch (2.0.0)
+ jekyll-watch (2.1.2)
listen (~> 3.0)
jwt (2.1.0)
kramdown (1.17.0)
launchy (2.4.3)
addressable (~> 2.3)
- liquid (4.0.0)
+ liquid (4.0.1)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
@@ -159,7 +159,7 @@ GEM
loofah (2.2.2)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
- mail (2.7.0)
+ mail (2.7.1)
mini_mime (>= 0.1.1)
marcel (0.3.3)
mimemagic (~> 0.3.2)
@@ -228,10 +228,10 @@ GEM
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
rotp (3.3.1)
- rouge (3.2.1)
+ rouge (3.3.0)
rspec-core (3.8.0)
rspec-support (~> 3.8.0)
- rspec-expectations (3.8.1)
+ rspec-expectations (3.8.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-mocks (3.8.0)
@@ -254,11 +254,13 @@ GEM
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
+ rubocop-rspec (1.30.0)
+ rubocop (>= 0.58.0)
ruby-progressbar (1.10.0)
ruby_dep (1.5.0)
rubyzip (1.2.2)
safe_yaml (1.0.4)
- saml-kit (1.0.24)
+ saml-kit (1.0.25)
activemodel (>= 4.2.0)
net-hippie (~> 0.1.8)
xml-kit (>= 0.1.13, <= 1.0.0)
@@ -269,9 +271,9 @@ GEM
rb-inotify (~> 0.9, >= 0.9.7)
scim-shady (0.2.1)
activesupport (>= 4.2.0)
- selenium-webdriver (3.14.0)
+ selenium-webdriver (3.14.1)
childprocess (~> 0.5)
- rubyzip (~> 1.2)
+ rubyzip (~> 1.2, >= 1.2.2)
spank (1.0.1441140881)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
@@ -318,7 +320,7 @@ GEM
xmldsig (~> 0.6)
xmldsig (0.6.6)
nokogiri (>= 1.6.8, < 2.0.0)
- xpath (3.1.0)
+ xpath (3.2.0)
nokogiri (~> 1.8)
PLATFORMS
@@ -353,8 +355,9 @@ DEPENDENCIES
rails (~> 5.2.0)
rails_12factor (~> 0.0)
rotp (~> 3.3)
- rspec-rails (~> 3.7)
- rubocop (~> 0.58)
+ rspec-rails (~> 3.8)
+ rubocop (~> 0.59)
+ rubocop-rspec (~> 1.30)
saml-kit (~> 1.0)
scim-shady (~> 0.2)
selenium-webdriver (~> 3.14)