osso 0.0.3.22 → 0.0.3.27

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f017aca126a5a5d516394ae1f13be3eaa91deb759f602571ba372fec06b1ee2
4
- data.tar.gz: ccac449dd89bf23b16a56a190837b2deeffc85bee95f7d7e6f7fd245b9762e17
3
+ metadata.gz: 61667cd9c9821cbbd903bd5ea753dfb67338a801e442b5e7ce21d77e06e2ada6
4
+ data.tar.gz: ef180637bd4da14905549a161e153e4a7b6b1dc9b9079d0e98952f7730e6d6ba
5
5
  SHA512:
6
- metadata.gz: e80f57d49d9a440d6e77c9b1b69a73e44b5ef28fda17289c19b1ed9a7ad5157391544899ae4b0d783864200faab119bf92343fe6f20b13ed2820068d5ceb80aa
7
- data.tar.gz: 9fc798d93fb897fb166a7234f9fef33d2d4fcf4d9df703f77963a604fe2b664ba7bd1f577079e787ea5706de6e22eed75138151d435a5ace8a1ab1d32af09285
6
+ metadata.gz: 59dde627b70d7f46a680dbf00ae260cd98613a679daaf14a2a08e7ce6aef7ab627ecf8b1ba8a459438b518f85e24b2db1e63375814c526f12733284fbbdc5a78
7
+ data.tar.gz: '09a8f59725b4a61aad3a92e8a676377d798049da26951882903c40a8ab7e2945e153097d4e94d896dc601d1d1fc01f518d05adc58af141ce56bc4a12697f40ab'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- osso (0.0.3.22)
4
+ osso (0.0.3.27)
5
5
  activesupport (>= 6.0.3.2)
6
6
  graphql
7
7
  jwt
@@ -11,8 +11,23 @@ module Osso
11
11
  data.merge(errors: [])
12
12
  end
13
13
 
14
- def response_error(error)
15
- error.merge(data: nil)
14
+ def response_error(errors)
15
+ raise ::GraphQL::ExecutionError.new(
16
+ 'Mutation error',
17
+ extensions: {
18
+ 'errors' => field_errors(errors),
19
+ }
20
+ )
21
+ end
22
+
23
+ def field_errors(errors)
24
+ errors.map do |attribute, messages|
25
+ attribute = attribute.to_s.camelize(:lower)
26
+ {
27
+ attribute: attribute,
28
+ message: messages,
29
+ }
30
+ end
16
31
  end
17
32
 
18
33
  def ready?(**args)
@@ -10,15 +10,14 @@ module Osso
10
10
  argument :sso_url, String, required: false
11
11
  argument :sso_cert, String, required: false
12
12
 
13
- field :identity_provider, Types::IdentityProvider, null: false
14
- field :errors, [String], null: false
13
+ field :identity_provider, Types::IdentityProvider, null: true
15
14
 
16
15
  def resolve(**args)
17
16
  provider = identity_provider(**args)
18
17
 
19
18
  return response_data(identity_provider: provider) if provider.update(args)
20
-
21
- response_error(errors: provider.errors.messages)
19
+
20
+ response_error(provider.errors)
22
21
  end
23
22
 
24
23
  def domain(**args)
@@ -19,7 +19,7 @@ module Osso
19
19
 
20
20
  return response_data(enterprise_account: enterprise_account) if enterprise_account.save
21
21
 
22
- response_error(errors: enterprise_account.errors.full_messages)
22
+ response_error(enterprise_account.errors)
23
23
  end
24
24
 
25
25
  def find_client_db_id(oauth_client_identifier)
@@ -23,7 +23,7 @@ module Osso
23
23
 
24
24
  return response_data(identity_provider: identity_provider) if identity_provider.save
25
25
 
26
- response_error(errors: identity_provider.errors.full_messages)
26
+ response_error(identity_provider.errors)
27
27
  end
28
28
 
29
29
  def domain(**args)
@@ -16,7 +16,7 @@ module Osso
16
16
 
17
17
  return response_data(oauth_client: oauth_client) if oauth_client.save
18
18
 
19
- response_error(errors: oauth_client.errors.full_messages)
19
+ response_error(oauth_client.errors)
20
20
  end
21
21
 
22
22
  def ready?(*)
@@ -20,7 +20,7 @@ module Osso
20
20
 
21
21
  return response_data(enterprise_account: nil) if customer.destroy
22
22
 
23
- response_error(errors: customer.errors.full_messages)
23
+ response_error(customer.errors)
24
24
  end
25
25
 
26
26
  def domain(**args)
@@ -16,7 +16,7 @@ module Osso
16
16
 
17
17
  return response_data(oauth_client: nil) if oauth_client.destroy
18
18
 
19
- response_error(errors: oauth_client.errors.full_messages)
19
+ response_error(oauth_client.errors)
20
20
  end
21
21
 
22
22
  def ready?(*)
@@ -13,11 +13,11 @@ module Osso
13
13
 
14
14
  def resolve(id:)
15
15
  oauth_client = Osso::Models::OauthClient.find(id)
16
- oauth_client.generate_secrets
16
+ oauth_client.regenerate_secrets!
17
17
 
18
18
  return response_data(oauth_client: oauth_client) if oauth_client.save
19
19
 
20
- response_error(errors: oauth_client.errors.full_messages)
20
+ response_error(oauth_client.errors)
21
21
  end
22
22
 
23
23
  def ready?(*)
@@ -20,7 +20,7 @@ module Osso
20
20
 
21
21
  response_data(oauth_client: oauth_client.reload)
22
22
  rescue StandardError => e
23
- response_error(errors: e)
23
+ response_error(e)
24
24
  end
25
25
 
26
26
  def ready?(*)
@@ -33,17 +33,17 @@ module Osso
33
33
 
34
34
  if updating_index
35
35
  updating = redirect_uris.delete_at(updating_index)
36
- redirect.update(updating.to_h)
36
+ redirect.update!(updating.to_h)
37
37
  next
38
38
  end
39
39
 
40
- redirect.destroy
40
+ redirect.destroy!
41
41
  end
42
42
  end
43
43
 
44
44
  def create_new(oauth_client, redirect_uris)
45
45
  redirect_uris.map do |uri|
46
- oauth_client.redirect_uris.create(uri.to_h.without(:id))
46
+ oauth_client.redirect_uris.create!(uri.to_h.without(:id))
47
47
  end
48
48
  end
49
49
  end
@@ -17,7 +17,7 @@ module Osso
17
17
  app_config = Osso::Models::AppConfig.find
18
18
  return response_data(app_config: app_config) if app_config.update(**args)
19
19
 
20
- response_error(errors: e)
20
+ response_error(app_config.errors)
21
21
  end
22
22
 
23
23
  def ready?(*)
@@ -11,6 +11,7 @@ require_relative 'types/base_enum'
11
11
  require_relative 'types/base_input_object'
12
12
  require_relative 'types/admin_user'
13
13
  require_relative 'types/app_config'
14
+ require_relative 'types/error'
14
15
  require_relative 'types/identity_provider_service'
15
16
  require_relative 'types/identity_provider_status'
16
17
  require_relative 'types/identity_provider'
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module Osso
6
+ module GraphQL
7
+ module Types
8
+ class Error < Types::BaseObject
9
+ description 'A mutation error'
10
+
11
+ field :attribute, String, null: false
12
+ field :message, String, null: false
13
+
14
+ def self.authorized?(_object, _context)
15
+ true
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -4,11 +4,14 @@ module Osso
4
4
  module Models
5
5
  # Base class for SAML Providers
6
6
  class IdentityProvider < ActiveRecord::Base
7
- NAME_FORMAT = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
8
7
  belongs_to :enterprise_account
9
8
  belongs_to :oauth_client
10
9
  has_many :users
11
10
  before_save :set_status
11
+ validate :sso_cert_valid
12
+
13
+ PEM_HEADER = "-----BEGIN CERTIFICATE-----\n"
14
+ PEM_FOOTER = "\n-----END CERTIFICATE-----"
12
15
 
13
16
  def name
14
17
  service.titlecase
@@ -41,11 +44,35 @@ module Osso
41
44
  self.status = 'CONFIGURED' if sso_url && sso_cert
42
45
  end
43
46
 
47
+ def active!
48
+ update(status: 'ACTIVE')
49
+ end
50
+
51
+ def error!
52
+ update(status: 'ERROR')
53
+ end
54
+
44
55
  def root_url
45
- return "https://${ENV['HEROKU_APP_NAME]}.herokuapp.com" if ENV['HEROKU_APP_NAME']
56
+ return "https://#{ENV['HEROKU_APP_NAME']}.herokuapp.com" if ENV['HEROKU_APP_NAME']
46
57
 
47
58
  ENV.fetch('BASE_URL')
48
59
  end
60
+
61
+ def sso_cert_valid
62
+ return if sso_cert.blank?
63
+
64
+ has_header_and_footer = sso_cert.match(/#{PEM_HEADER}(?<cert>.*)#{PEM_FOOTER}/m)
65
+
66
+ if has_header_and_footer
67
+ OpenSSL::X509::Certificate.new(sso_cert)
68
+ self.sso_cert = has_header_and_footer[:cert]
69
+ else
70
+ OpenSSL::X509::Certificate.new([PEM_HEADER, sso_cert, PEM_FOOTER].join)
71
+ end
72
+
73
+ rescue OpenSSL::X509::CertificateError
74
+ errors.add(:sso_cert, 'x509 Certificate is malformed')
75
+ end
49
76
  end
50
77
  end
51
78
  end
@@ -26,6 +26,11 @@ module Osso
26
26
  self.identifier ||= SecureRandom.hex(16)
27
27
  self.secret ||= SecureRandom.hex(32)
28
28
  end
29
+
30
+ def regenerate_secrets!
31
+ self.identifier = SecureRandom.hex(16)
32
+ self.secret = SecureRandom.hex(32)
33
+ end
29
34
  end
30
35
  end
31
36
  end
@@ -41,6 +41,7 @@ module Osso
41
41
  provider = Models::IdentityProvider.find(params[:id])
42
42
  @oauth_client = provider.oauth_client
43
43
 
44
+ # TODO: PORC for validating attributes
44
45
  attributes = env['omniauth.auth']&.
45
46
  extra&.
46
47
  response_object&.
@@ -58,8 +59,7 @@ module Osso
58
59
  oauth_client: @oauth_client,
59
60
  redirect_uri: redirect_uri,
60
61
  )
61
-
62
- # Mark IDP as active
62
+ provider.active!
63
63
 
64
64
  redirect(redirect_uri + "?code=#{CGI.escape(authorization_code.token)}&state=#{provider_state}")
65
65
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Osso
4
- VERSION = '0.0.3.22'
4
+ VERSION = '0.0.3.27'
5
5
  end
@@ -5,6 +5,7 @@ FactoryBot.define do
5
5
  id { SecureRandom.uuid }
6
6
  domain { Faker::Internet.domain_name }
7
7
  oauth_client
8
+ status { 'PENDING' }
8
9
 
9
10
  factory :okta_identity_provider, parent: :identity_provider do
10
11
  service { 'OKTA' }
@@ -21,6 +22,7 @@ FactoryBot.define do
21
22
  end
22
23
 
23
24
  factory :configured_identity_provider, parent: :identity_provider do
25
+ status { 'CONFIGURED' }
24
26
  sso_cert do
25
27
  <<~CERT
26
28
  -----BEGIN CERTIFICATE-----
@@ -12,7 +12,7 @@ describe Osso::GraphQL::Schema do
12
12
  id: identity_provider.id,
13
13
  service: 'OKTA',
14
14
  ssoUrl: 'https://example.com',
15
- ssoCert: 'BEGIN_CERTIFICATE',
15
+ ssoCert: valid_x509_pem,
16
16
  },
17
17
  }
18
18
  end
@@ -6,11 +6,20 @@ describe Osso::Models::IdentityProvider do
6
6
  subject { create(:okta_identity_provider) }
7
7
 
8
8
  describe '#assertion_consumer_service_url' do
9
- it 'returns the expected URI' do
9
+ it 'returns the expected URI for BASE_URL' do
10
+ ENV['HEROKU_APP_NAME'] = nil
10
11
  ENV['BASE_URL'] = 'https://example.com'
11
12
 
12
13
  expect(subject.assertion_consumer_service_url).to eq(
13
- "https://example.com/auth/saml/#{subject.id}/callback",
14
+ "#{ENV['BASE_URL']}/auth/saml/#{subject.id}/callback",
15
+ )
16
+ end
17
+
18
+ it 'returns the expected URI for HEROKU_APP_NAME' do
19
+ ENV['HEROKU_APP_NAME'] = 'test'
20
+
21
+ expect(subject.assertion_consumer_service_url).to eq(
22
+ "https://test.herokuapp.com/auth/saml/#{subject.id}/callback",
14
23
  )
15
24
  end
16
25
  end
@@ -26,4 +35,25 @@ describe Osso::Models::IdentityProvider do
26
35
  )
27
36
  end
28
37
  end
38
+
39
+ describe '#validate_sso_cert' do
40
+ it 'rejects an invalid cert' do
41
+ subject.update(sso_cert: 'bad-cert')
42
+
43
+ expect(subject.errors.full_messages.first).to include('x509 Certificate is malformed')
44
+ end
45
+
46
+ it 'massages a cert with header and footer' do
47
+ subject.update(sso_cert: valid_x509_pem)
48
+
49
+ expect(subject.errors).to be_empty
50
+ expect(subject.sso_cert).to_not include('BEGIN CERTIFICATE')
51
+ end
52
+
53
+ it 'accepts a cert without header and footer' do
54
+ subject.update(sso_cert: raw_x509_string)
55
+
56
+ expect(subject.errors).to be_empty
57
+ end
58
+ end
29
59
  end
@@ -104,6 +104,17 @@ describe Osso::Auth do
104
104
  )
105
105
  end.to_not(change { Osso::Models::User.count })
106
106
  end
107
+ it 'marks the provider as ACTIVE' do
108
+ post(
109
+ "/auth/saml/#{okta_provider.id}/callback",
110
+ nil,
111
+ {
112
+ 'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
113
+ 'identity_provider' => okta_provider,
114
+ },
115
+ )
116
+ expect(okta_provider.reload.status).to eq('ACTIVE')
117
+ end
107
118
  end
108
119
  end
109
120
 
@@ -126,6 +137,21 @@ describe Osso::Auth do
126
137
  )
127
138
  end.to change { Osso::Models::User.count }.by(1)
128
139
  end
140
+
141
+ it 'marks the provider ACTIVE' do
142
+ mock_saml_omniauth
143
+
144
+ post(
145
+ "/auth/saml/#{azure_provider.id}/callback",
146
+ nil,
147
+ {
148
+ 'omniauth.auth' => OmniAuth.config.mock_auth[:saml],
149
+ 'identity_provider' => azure_provider,
150
+ },
151
+ )
152
+
153
+ expect(azure_provider.reload.status).to eq('ACTIVE')
154
+ end
129
155
  end
130
156
 
131
157
  describe 'on subsequent authentications' do
@@ -21,6 +21,9 @@ require File.expand_path '../lib/osso.rb', __dir__
21
21
  require File.expand_path 'support/spec_app', __dir__
22
22
 
23
23
  module RSpecMixin
24
+ PEM_HEADER = "-----BEGIN CERTIFICATE-----\n"
25
+ PEM_FOOTER = "\n-----END CERTIFICATE-----"
26
+
24
27
  include Rack::Test::Methods
25
28
 
26
29
  def app
@@ -46,6 +49,16 @@ module RSpecMixin
46
49
  def spec_views
47
50
  File.dirname(__FILE__) + '/support/views'
48
51
  end
52
+
53
+ def valid_x509_pem
54
+ raw = File.read(File.dirname(__FILE__) + '/support/fixtures/test.pem')
55
+ OpenSSL::X509::Certificate.new(raw).to_pem
56
+ end
57
+
58
+ def raw_x509_string
59
+ raw = valid_x509_pem.match(/#{PEM_HEADER}(?<cert>.*)#{PEM_FOOTER}/m)
60
+ raw[:cert]
61
+ end
49
62
  end
50
63
 
51
64
  RSpec.configure do |config|
@@ -0,0 +1,30 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANDYf/XXreldztPH
3
+ 0IJSkvgeQVZCu1ie+D2Ij9nEzJAuM10bD4p2IjI8EQUKUTn1OBbX4ykhn5Ovw9SU
4
+ D8qEllQCzq7zuX54BEm8cHX3IXTpMLM90T2zJayS+3hQqXHzGXeX3fuP1KhNrrkL
5
+ HAlDEtVD/vwIci/TS2Mep7weytOjAgMBAAECgYA23kpwCmQUhaLLHRn4wzz9luVP
6
+ hmS2Gb3aXMB+VCfyUVEJSwzAMd02GXXXPyir83Ly/XEe40iLgogOl3+2kzLzEegI
7
+ 1wx9mydlar0kBIDKJkYdnikbvy0IKFNXRxqHl0Oecy9lArDKmBmadYsse8hsZLX2
8
+ eZUmB8G50TeDyz/4gQJBAO1OM4dT/Uo/zTaTDMs7A+td1C4gvjpI7aKPwHdwmvoc
9
+ dQN9BKoAV0EMoUcXvAeVreWEYGZrMwXQB6xT7aydyUMCQQDhTFffVlvsqOmQYhzf
10
+ lbKS8orI0SZZHz8F5dnj1zwb4Xp+hl6tIAkxcZ1DxP4emfc1htd4GswzNqSVfFJv
11
+ JXYhAkEAsmdWSekUxVtN9jd7KNbHTY2O1Nb87GijbtFPyvu3J015kxPMC9qRvm+2
12
+ V/I6BCG9SI3Kw3TYOQh6nE3Eoz9EbQJBAKvzm4F+pOwsQw8KguT2mPNkoB4C2xTc
13
+ LzquIi2t4VeaMOaOYYYa1EljYFcP66+pbS7yOlOViFJyGw1odHYWDmECQCBGi7f5
14
+ qT4Bs3DoaIyD0w9F3LY/ny7+Pa7WGUqQvQWygUDObBtwojXhg/A9BGckUrQ2jmu/
15
+ bhqnqQJs3f05ETA=
16
+ -----END PRIVATE KEY-----
17
+ -----BEGIN CERTIFICATE-----
18
+ MIICDTCCAXYCCQCm0tqsG7zO2TANBgkqhkiG9w0BAQsFADBLMQswCQYDVQQGEwJV
19
+ UzERMA8GA1UECAwITmV3IFlvcmsxETAPBgNVBAcMCEJyb29rbHluMRYwFAYDVQQK
20
+ DA1FbnRlcnByaXNlT1NTMB4XDTIwMDkwMjE0MTEyMVoXDTIxMDkwMjE0MTEyMVow
21
+ SzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREwDwYDVQQHDAhCcm9v
22
+ a2x5bjEWMBQGA1UECgwNRW50ZXJwcmlzZU9TUzCBnzANBgkqhkiG9w0BAQEFAAOB
23
+ jQAwgYkCgYEA0Nh/9det6V3O08fQglKS+B5BVkK7WJ74PYiP2cTMkC4zXRsPinYi
24
+ MjwRBQpROfU4FtfjKSGfk6/D1JQPyoSWVALOrvO5fngESbxwdfchdOkwsz3RPbMl
25
+ rJL7eFCpcfMZd5fd+4/UqE2uuQscCUMS1UP+/AhyL9NLYx6nvB7K06MCAwEAATAN
26
+ BgkqhkiG9w0BAQsFAAOBgQDGze/POq+GSwOIYftr83+YkNTIQAg+bl8hiFMtJ3OV
27
+ buFsI/oUGaKloXOrDLbygk+lvimFbj36k3IhwRI7iXJDCwZGxtVCC4+8VNqqT1Yj
28
+ uZT9xHGYVszzGc8nz4wcaQ8M/W4mCuXet1qDwAi0Zo9yLBnyEdc6pluDdJuz0cg6
29
+ xQ==
30
+ -----END CERTIFICATE-----
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: osso
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3.22
4
+ version: 0.0.3.27
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Bauch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-27 00:00:00.000000000 Z
11
+ date: 2020-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -231,7 +231,6 @@ executables:
231
231
  extensions: []
232
232
  extra_rdoc_files: []
233
233
  files:
234
- - ".DS_Store"
235
234
  - ".buildkite/hooks/environment"
236
235
  - ".buildkite/hooks/pre-command"
237
236
  - ".buildkite/pipeline.yml"
@@ -299,6 +298,7 @@ files:
299
298
  - lib/osso/graphql/types/base_input_object.rb
300
299
  - lib/osso/graphql/types/base_object.rb
301
300
  - lib/osso/graphql/types/enterprise_account.rb
301
+ - lib/osso/graphql/types/error.rb
302
302
  - lib/osso/graphql/types/identity_provider.rb
303
303
  - lib/osso/graphql/types/identity_provider_service.rb
304
304
  - lib/osso/graphql/types/identity_provider_status.rb
@@ -346,14 +346,13 @@ files:
346
346
  - spec/graphql/query/identity_provider_spec.rb
347
347
  - spec/graphql/query/oauth_clients_spec.rb
348
348
  - spec/helpers/auth_spec.rb
349
- - spec/models/azure_saml_provider_spec.rb
350
349
  - spec/models/identity_provider_spec.rb
351
- - spec/models/okta_saml_provider_spec.rb
352
350
  - spec/routes/admin_spec.rb
353
351
  - spec/routes/app_spec.rb
354
352
  - spec/routes/auth_spec.rb
355
353
  - spec/routes/oauth_spec.rb
356
354
  - spec/spec_helper.rb
355
+ - spec/support/fixtures/test.pem
357
356
  - spec/support/spec_app.rb
358
357
  - spec/support/views/admin.erb
359
358
  - spec/support/views/error.erb
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- # describe Osso::Models::AzureSamlProvider do
6
- # subject { create(:azure_identity_provider) }
7
-
8
- # describe '#saml_options' do
9
- # it 'returns the required args' do
10
- # expect(subject.saml_options).
11
- # to match(
12
- # domain: subject.domain,
13
- # idp_cert: subject.idp_cert,
14
- # idp_sso_target_url: subject.idp_sso_target_url,
15
- # issuer: "id:#{subject.id}",
16
- # )
17
- # end
18
- # end
19
- # end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- # describe Osso::Models::OktaSamlProvider do
6
- # subject { create(:okta_identity_provider) }
7
-
8
- # describe '#saml_options' do
9
- # it 'returns the required args' do
10
- # expect(subject.saml_options).
11
- # to match(
12
- # domain: subject.domain,
13
- # idp_cert: subject.idp_cert,
14
- # idp_sso_target_url: subject.idp_sso_target_url,
15
- # issuer: subject.id,
16
- # name_identifier_format: described_class::NAME_FORMAT,
17
- # )
18
- # end
19
- # end
20
- # end