osso 0.0.5.pre.lambda → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.buildkite/pipeline.yml +6 -4
  3. data/.github/dependabot.yml +8 -0
  4. data/.github/workflows/automerge.yml +19 -0
  5. data/.rubocop.yml +4 -1
  6. data/Gemfile +1 -1
  7. data/Gemfile.lock +48 -27
  8. data/bin/annotate +3 -1
  9. data/db/schema.rb +40 -3
  10. data/lib/osso.rb +0 -1
  11. data/lib/osso/db/migrate/20201023142158_add_rodauth_tables.rb +47 -0
  12. data/lib/osso/db/migrate/20201105122026_add_token_index_to_access_tokens.rb +5 -0
  13. data/lib/osso/db/migrate/20201106154936_add_requested_to_authorization_codes_and_access_tokens.rb +6 -0
  14. data/lib/osso/db/migrate/20201109160851_add_sso_issuer_to_identity_providers.rb +12 -0
  15. data/lib/osso/db/migrate/20201110190754_remove_oauth_client_id_from_enterprise_accounts.rb +9 -0
  16. data/lib/osso/db/migrate/20201112160120_add_ping_to_identity_provider_service_enum.rb +28 -0
  17. data/lib/osso/error/account_configuration_error.rb +1 -0
  18. data/lib/osso/error/oauth_error.rb +6 -3
  19. data/lib/osso/graphql/mutation.rb +1 -0
  20. data/lib/osso/graphql/mutations.rb +1 -0
  21. data/lib/osso/graphql/mutations/create_enterprise_account.rb +0 -7
  22. data/lib/osso/graphql/mutations/create_identity_provider.rb +7 -6
  23. data/lib/osso/graphql/mutations/invite_admin_user.rb +43 -0
  24. data/lib/osso/graphql/query.rb +8 -0
  25. data/lib/osso/graphql/resolvers/enterprise_accounts.rb +2 -2
  26. data/lib/osso/graphql/types.rb +2 -2
  27. data/lib/osso/graphql/types/admin_user.rb +9 -0
  28. data/lib/osso/graphql/types/base_object.rb +1 -1
  29. data/lib/osso/graphql/types/identity_provider.rb +2 -0
  30. data/lib/osso/graphql/types/identity_provider_service.rb +2 -1
  31. data/lib/osso/lib/app_config.rb +1 -1
  32. data/lib/osso/lib/route_map.rb +0 -16
  33. data/lib/osso/lib/saml_handler.rb +5 -0
  34. data/lib/osso/models/access_token.rb +4 -2
  35. data/lib/osso/models/account.rb +34 -0
  36. data/lib/osso/models/authorization_code.rb +2 -1
  37. data/lib/osso/models/enterprise_account.rb +3 -1
  38. data/lib/osso/models/identity_provider.rb +18 -4
  39. data/lib/osso/models/models.rb +1 -0
  40. data/lib/osso/models/oauth_client.rb +0 -1
  41. data/lib/osso/routes/admin.rb +39 -33
  42. data/lib/osso/routes/auth.rb +9 -9
  43. data/lib/osso/routes/oauth.rb +34 -16
  44. data/lib/osso/version.rb +1 -1
  45. data/lib/osso/views/admin.erb +5 -0
  46. data/lib/osso/views/error.erb +1 -0
  47. data/lib/osso/views/layout.erb +0 -0
  48. data/lib/osso/views/multiple_providers.erb +1 -0
  49. data/lib/osso/views/welcome.erb +0 -0
  50. data/lib/tasks/bootstrap.rake +25 -4
  51. data/osso-rb.gemspec +5 -0
  52. data/spec/factories/account.rb +24 -0
  53. data/spec/factories/enterprise_account.rb +11 -3
  54. data/spec/factories/identity_providers.rb +10 -2
  55. data/spec/factories/user.rb +4 -0
  56. data/spec/graphql/mutations/configure_identity_provider_spec.rb +1 -1
  57. data/spec/graphql/mutations/create_enterprise_account_spec.rb +0 -14
  58. data/spec/graphql/mutations/create_identity_provider_spec.rb +59 -8
  59. data/spec/graphql/query/identity_provider_spec.rb +2 -2
  60. data/spec/models/enterprise_account_spec.rb +18 -0
  61. data/spec/models/identity_provider_spec.rb +24 -3
  62. data/spec/routes/admin_spec.rb +7 -41
  63. data/spec/routes/auth_spec.rb +17 -18
  64. data/spec/routes/oauth_spec.rb +87 -5
  65. data/spec/spec_helper.rb +3 -3
  66. data/spec/support/views/layout.erb +1 -0
  67. metadata +98 -7
  68. data/lib/osso/helpers/auth.rb +0 -94
  69. data/lib/osso/helpers/helpers.rb +0 -8
  70. data/spec/helpers/auth_spec.rb +0 -269
@@ -0,0 +1,9 @@
1
+ class RemoveOauthClientIdFromEnterpriseAccounts < ActiveRecord::Migration[6.0]
2
+ def up
3
+ remove_reference :enterprise_accounts, :oauth_client, index: true
4
+ end
5
+
6
+ def down
7
+ add_reference :enterprise_accounts, :oauth_client, type: :uuid, index: true
8
+ end
9
+ end
@@ -0,0 +1,28 @@
1
+ class AddPingToIdentityProviderServiceEnum < ActiveRecord::Migration[6.0]
2
+ disable_ddl_transaction!
3
+
4
+ def up
5
+ execute <<-SQL
6
+ ALTER TYPE identity_provider_service ADD VALUE 'PING';
7
+ SQL
8
+ end
9
+
10
+ def down
11
+ execute <<~SQL
12
+ CREATE TYPE identity_provider_service_new AS ENUM ('AZURE', 'OKTA', 'ONELOGIN', 'GOOGLE');
13
+
14
+ -- Remove values that won't be compatible with new definition
15
+ DELETE FROM identity_providers WHERE service = 'PING';
16
+
17
+ -- Convert to new type, casting via text representation
18
+ ALTER TABLE identity_providers
19
+ ALTER COLUMN service TYPE identity_provider_service_new
20
+ USING (service::text::identity_provider_service_new);
21
+
22
+ -- and swap the types
23
+ DROP TYPE identity_provider_service;
24
+
25
+ ALTER TYPE identity_provider_service_new RENAME TO identity_provider_service;
26
+ SQL
27
+ end
28
+ end
@@ -6,6 +6,7 @@ module Osso
6
6
 
7
7
  class MissingConfiguredIdentityProvider < AccountConfigurationError
8
8
  def initialize(domain: 'The requested domain')
9
+ super
9
10
  @domain = domain
10
11
  end
11
12
 
@@ -10,6 +10,7 @@ module Osso
10
10
 
11
11
  class NoAccountForOAuthClientError < OAuthError
12
12
  def initialize(domain: 'the requested domain')
13
+ super
13
14
  @domain = domain
14
15
  end
15
16
 
@@ -30,13 +31,15 @@ module Osso
30
31
 
31
32
  class InvalidRedirectUri < OAuthError
32
33
  def initialize(redirect_uri:)
34
+ super
33
35
  @redirect_uri = redirect_uri
34
36
  end
35
37
 
36
38
  def message
37
- "The requested redirect URI #{@redirect_uri} is not on the allow-list for the rquested OAuth client identifier. " \
38
- "Review our OAuth documentation, check you're using the correct OAuth client identifier, " \
39
- 'and confirm your Redirect URI allow-list includes the appropriate URI(s).'
39
+ "The requested redirect URI #{@redirect_uri} is not on the allow-list for the rquested " \
40
+ 'OAuth client identifier. Review our OAuth documentation, check you\'re using the correct ' \
41
+ 'OAuth client identifier, and confirm your Redirect URI allow-list includes the ' \
42
+ 'appropriate URI(s).'
40
43
  end
41
44
  end
42
45
  end
@@ -13,6 +13,7 @@ module Osso
13
13
  field :delete_enterprise_account, mutation: Mutations::DeleteEnterpriseAccount
14
14
  field :delete_identity_provider, mutation: Mutations::DeleteIdentityProvider
15
15
  field :delete_oauth_client, mutation: Mutations::DeleteOauthClient
16
+ field :invite_admin_user, mutation: Mutations::InviteAdminUser
16
17
  field :set_redirect_uris, mutation: Mutations::SetRedirectUris
17
18
  field :regenerate_oauth_credentials, mutation: Mutations::RegenerateOauthCredentials
18
19
  field :update_app_config, mutation: Mutations::UpdateAppConfig
@@ -13,6 +13,7 @@ require_relative 'mutations/create_oauth_client'
13
13
  require_relative 'mutations/delete_enterprise_account'
14
14
  require_relative 'mutations/delete_identity_provider'
15
15
  require_relative 'mutations/delete_oauth_client'
16
+ require_relative 'mutations/invite_admin_user'
16
17
  require_relative 'mutations/regenerate_oauth_credentials'
17
18
  require_relative 'mutations/set_redirect_uris'
18
19
  require_relative 'mutations/update_app_config'
@@ -8,24 +8,17 @@ module Osso
8
8
 
9
9
  argument :domain, String, required: true
10
10
  argument :name, String, required: true
11
- argument :oauth_client_id, String, required: false
12
11
 
13
12
  field :enterprise_account, Types::EnterpriseAccount, null: false
14
13
  field :errors, [String], null: false
15
14
 
16
15
  def resolve(**args)
17
16
  enterprise_account = Osso::Models::EnterpriseAccount.new(args)
18
- enterprise_account.oauth_client_id ||= find_client_db_id(context[:oauth_client_id])
19
17
 
20
18
  return response_data(enterprise_account: enterprise_account) if enterprise_account.save
21
19
 
22
20
  response_error(enterprise_account.errors)
23
21
  end
24
-
25
- def find_client_db_id(oauth_client_identifier)
26
- Osso::Models::OauthClient.find_by(identifier: oauth_client_identifier).
27
- id
28
- end
29
22
  end
30
23
  end
31
24
  end
@@ -8,17 +8,18 @@ module Osso
8
8
 
9
9
  argument :enterprise_account_id, ID, required: true
10
10
  argument :service, Types::IdentityProviderService, required: false
11
+ argument :oauth_client_id, String, required: true
11
12
 
12
13
  field :identity_provider, Types::IdentityProvider, null: false
13
14
  field :errors, [String], null: false
14
15
 
15
- def resolve(service: nil, **args)
16
- customer = enterprise_account(**args)
16
+ def resolve(service: nil, enterprise_account_id:, oauth_client_id:)
17
+ customer = enterprise_account(enterprise_account_id: enterprise_account_id)
17
18
 
18
19
  identity_provider = customer.identity_providers.build(
19
20
  service: service,
20
21
  domain: customer.domain,
21
- oauth_client_id: customer.oauth_client_id,
22
+ oauth_client_id: oauth_client_id,
22
23
  )
23
24
 
24
25
  return response_data(identity_provider: identity_provider) if identity_provider.save
@@ -26,11 +27,11 @@ module Osso
26
27
  response_error(identity_provider.errors)
27
28
  end
28
29
 
29
- def domain(**args)
30
- enterprise_account(**args)&.domain
30
+ def domain(enterprise_account_id:, **args)
31
+ enterprise_account(enterprise_account_id: enterprise_account_id)&.domain
31
32
  end
32
33
 
33
- def enterprise_account(enterprise_account_id:, **_args)
34
+ def enterprise_account(enterprise_account_id:)
34
35
  @enterprise_account ||= Osso::Models::EnterpriseAccount.find(enterprise_account_id)
35
36
  end
36
37
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Mutations
6
+ class InviteAdminUser < BaseMutation
7
+ null false
8
+
9
+ argument :email, String, required: true
10
+ argument :oauth_client_id, ID, required: false
11
+ argument :role, String, required: true
12
+
13
+ field :admin_user, Types::AdminUser, null: true
14
+ field :errors, [String], null: false
15
+
16
+ def resolve(email:, role:, oauth_client_id: nil)
17
+ admin_user = Osso::Models::Account.new(
18
+ email: email,
19
+ role: role,
20
+ oauth_client_id: oauth_client_id,
21
+ )
22
+
23
+ if admin_user.save
24
+ verify_user(email)
25
+
26
+ return response_data(admin_user: admin_user)
27
+ end
28
+
29
+ response_error(admin_user.errors)
30
+ end
31
+
32
+ def ready?(*)
33
+ admin_ready?
34
+ end
35
+
36
+ def verify_user(email)
37
+ context[:rodauth].account_from_login(email)
38
+ context[:rodauth].setup_account_verification
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -5,6 +5,7 @@ module Osso
5
5
  module Types
6
6
  class QueryType < ::GraphQL::Schema::Object
7
7
  field :enterprise_accounts, null: true, resolver: Resolvers::EnterpriseAccounts do
8
+ argument :search, String, required: false
8
9
  argument :sort_column, String, required: false
9
10
  argument :sort_order, String, required: false
10
11
  end
@@ -40,6 +41,13 @@ module Osso
40
41
  argument :id, ID, required: true
41
42
  end
42
43
 
44
+ field(
45
+ :admin_users,
46
+ [Types::AdminUser],
47
+ null: false,
48
+ resolve: ->(_obj, _args, _context) { Osso::Models::Account.all },
49
+ )
50
+
43
51
  field(
44
52
  :current_user,
45
53
  Types::AdminUser,
@@ -6,11 +6,11 @@ module Osso
6
6
  class EnterpriseAccounts < BaseResolver
7
7
  type Types::EnterpriseAccount.connection_type, null: true
8
8
 
9
- def resolve(sort_column: nil, sort_order: nil)
9
+ def resolve(sort_column: nil, sort_order: nil, search: nil)
10
10
  return Array(Osso::Models::EnterpriseAccount.find_by(domain: context_domain)) unless internal_authorized?
11
11
 
12
12
  accounts = Osso::Models::EnterpriseAccount
13
-
13
+ accounts = accounts.where('domain ilike ? OR name ilike ?', "%#{search}%", "%#{search}%") if search
14
14
  accounts = accounts.order(sort_column.underscore => sort_order_sym(sort_order)) if sort_column && sort_order
15
15
 
16
16
  accounts.all
@@ -14,8 +14,8 @@ require_relative 'types/app_config'
14
14
  require_relative 'types/error'
15
15
  require_relative 'types/identity_provider_service'
16
16
  require_relative 'types/identity_provider_status'
17
+ require_relative 'types/redirect_uri'
18
+ require_relative 'types/oauth_client'
17
19
  require_relative 'types/identity_provider'
18
20
  require_relative 'types/enterprise_account'
19
- require_relative 'types/redirect_uri'
20
21
  require_relative 'types/redirect_uri_input'
21
- require_relative 'types/oauth_client'
@@ -11,11 +11,20 @@ module Osso
11
11
  field :id, ID, null: false
12
12
  field :email, String, null: false
13
13
  field :scope, String, null: false
14
+ field :role, String, null: false
14
15
  field :oauth_client_id, ID, null: true
15
16
 
16
17
  def self.authorized?(_object, _context)
17
18
  true
18
19
  end
20
+
21
+ def created_at
22
+ 12.hours.ago
23
+ end
24
+
25
+ def updated_at
26
+ 12.hours.ago
27
+ end
19
28
  end
20
29
  end
21
30
  end
@@ -28,7 +28,7 @@ module Osso
28
28
  def self.authorized?(object, context)
29
29
  # we first receive the payload object as a hash, but can depend on the
30
30
  # return type to hide the actual objects non-admins shouldn't see
31
- return true if object.class == Hash
31
+ return true if object.instance_of?(Hash)
32
32
 
33
33
  internal_authorized?(context) || enterprise_authorized?(context, object&.domain)
34
34
  end
@@ -13,10 +13,12 @@ module Osso
13
13
  field :service, Types::IdentityProviderService, null: true
14
14
  field :domain, String, null: false
15
15
  field :acs_url, String, null: false
16
+ field :sso_issuer, String, null: false
16
17
  field :sso_url, String, null: true
17
18
  field :sso_cert, String, null: true
18
19
  field :status, Types::IdentityProviderStatus, null: false
19
20
  field :acs_url_validator, String, null: false
21
+ field :oauth_client, Types::OauthClient, null: false
20
22
  end
21
23
  end
22
24
  end
@@ -5,9 +5,10 @@ module Osso
5
5
  module Types
6
6
  class IdentityProviderService < BaseEnum
7
7
  value('AZURE', 'Microsoft Azure Identity Provider', value: 'AZURE')
8
+ value('GOOGLE', 'Google SAML Identity Provider', value: 'GOOGLE')
8
9
  value('OKTA', 'Okta Identity Provider', value: 'OKTA')
9
10
  value('ONELOGIN', 'OneLogin Identity Provider', value: 'ONELOGIN')
10
- value('GOOGLE', 'Google SAML Identity Provider', value: 'GOOGLE')
11
+ value('PING', 'PingID Identity Provider', value: 'PING')
11
12
  end
12
13
  end
13
14
  end
@@ -7,7 +7,7 @@ module Osso
7
7
  def self.included(klass)
8
8
  klass.class_eval do
9
9
  use Rack::JSONBodyParser
10
- use Rack::Session::Cookie, secret: ENV['SESSION_SECRET']
10
+ use Rack::Session::Cookie, secret: ENV.fetch('SESSION_SECRET')
11
11
 
12
12
  error ActiveRecord::RecordNotFound do
13
13
  status 404
@@ -1,29 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Metrics/MethodLength
4
-
5
3
  module Osso
6
4
  module RouteMap
7
5
  def self.included(klass)
8
6
  klass.class_eval do
9
-
10
7
  use Osso::Admin
11
8
  use Osso::Auth
12
9
  use Osso::Oauth
13
-
14
- post '/graphql' do
15
- token_protected!
16
-
17
- result = Osso::GraphQL::Schema.execute(
18
- params[:query],
19
- variables: params[:variables],
20
- context: current_user.symbolize_keys,
21
- )
22
-
23
- json result
24
- end
25
10
  end
26
11
  end
27
12
  end
28
13
  end
29
- # rubocop:enable Metrics/MethodLength
@@ -55,6 +55,7 @@ module Osso
55
55
  @authorization_code ||= user.authorization_codes.create!(
56
56
  oauth_client: provider.oauth_client,
57
57
  redirect_uri: redirect_uri_base,
58
+ requested: requested_param,
58
59
  )
59
60
  end
60
61
 
@@ -81,5 +82,9 @@ module Osso
81
82
  def valid_idp_initiated_flow
82
83
  !session[:osso_oauth_redirect_uri] && !session[:osso_oauth_state]
83
84
  end
85
+
86
+ def requested_param
87
+ @session.delete(:osso_oauth_requested)
88
+ end
84
89
  end
85
90
  end
@@ -39,9 +39,11 @@ end
39
39
  # updated_at :datetime not null
40
40
  # user_id :uuid
41
41
  # oauth_client_id :uuid
42
+ # requested :jsonb
42
43
  #
43
44
  # Indexes
44
45
  #
45
- # index_access_tokens_on_oauth_client_id (oauth_client_id)
46
- # index_access_tokens_on_user_id (user_id)
46
+ # index_access_tokens_on_oauth_client_id (oauth_client_id)
47
+ # index_access_tokens_on_token_and_expires_at (token,expires_at) UNIQUE
48
+ # index_access_tokens_on_user_id (user_id)
47
49
  #
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module Models
5
+ class Account < ::ActiveRecord::Base
6
+ enum status_id: { 1 => :Unverified, 2 => :Verified, 3 => :Closed }
7
+
8
+ def context
9
+ {
10
+ email: email,
11
+ id: id,
12
+ scope: role,
13
+ oauth_client_id: oauth_client_id,
14
+ }
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ # == Schema Information
21
+ #
22
+ # Table name: accounts
23
+ #
24
+ # id :uuid not null, primary key
25
+ # email :citext not null
26
+ # status_id :integer default(NULL), not null
27
+ # role :string default("admin"), not null
28
+ # oauth_client_id :string
29
+ #
30
+ # Indexes
31
+ #
32
+ # index_accounts_on_email (email) UNIQUE WHERE (status_id = ANY (ARRAY[1, 2]))
33
+ # index_accounts_on_oauth_client_id (oauth_client_id)
34
+ #
@@ -7,7 +7,7 @@ module Osso
7
7
 
8
8
  def access_token
9
9
  @access_token ||= expired! &&
10
- user.access_tokens.create(oauth_client: oauth_client)
10
+ user.access_tokens.create(oauth_client: oauth_client, requested: requested)
11
11
  end
12
12
  end
13
13
  end
@@ -25,6 +25,7 @@ end
25
25
  # updated_at :datetime not null
26
26
  # user_id :uuid
27
27
  # oauth_client_id :uuid
28
+ # requested :jsonb
28
29
  #
29
30
  # Indexes
30
31
  #
@@ -8,10 +8,11 @@ module Osso
8
8
  # includes fields for external IDs such that you can persist
9
9
  # your ID for an account in your Osso instance.
10
10
  class EnterpriseAccount < ActiveRecord::Base
11
- belongs_to :oauth_client
12
11
  has_many :users
13
12
  has_many :identity_providers
14
13
 
14
+ validates_format_of :domain, with: /\A[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,5}\z/
15
+
15
16
  def single_provider?
16
17
  identity_providers.not_pending.one?
17
18
  end
@@ -40,6 +41,7 @@ end
40
41
  # name :string not null
41
42
  # created_at :datetime not null
42
43
  # updated_at :datetime not null
44
+ # users_count :integer default(0)
43
45
  #
44
46
  # Indexes
45
47
  #