osso 0.0.5.pre.theta → 0.0.7

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 (78) 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 +2 -2
  7. data/Gemfile.lock +58 -37
  8. data/LICENSE +21 -23
  9. data/Rakefile +2 -0
  10. data/bin/annotate +3 -1
  11. data/db/schema.rb +41 -3
  12. data/lib/osso.rb +0 -1
  13. data/lib/osso/db/migrate/20200929154117_add_users_count_to_identity_providers_and_enterprise_accounts.rb +6 -0
  14. data/lib/osso/db/migrate/20201023142158_add_rodauth_tables.rb +47 -0
  15. data/lib/osso/db/migrate/20201105122026_add_token_index_to_access_tokens.rb +5 -0
  16. data/lib/osso/db/migrate/20201106154936_add_requested_to_authorization_codes_and_access_tokens.rb +6 -0
  17. data/lib/osso/db/migrate/20201109160851_add_sso_issuer_to_identity_providers.rb +12 -0
  18. data/lib/osso/db/migrate/20201110190754_remove_oauth_client_id_from_enterprise_accounts.rb +9 -0
  19. data/lib/osso/db/migrate/20201112160120_add_ping_to_identity_provider_service_enum.rb +28 -0
  20. data/lib/osso/db/migrate/20201125143501_add_salesforce_to_provider_service_enum.rb +28 -0
  21. data/lib/osso/error/account_configuration_error.rb +1 -0
  22. data/lib/osso/error/oauth_error.rb +6 -3
  23. data/lib/osso/graphql/mutation.rb +2 -0
  24. data/lib/osso/graphql/mutations.rb +2 -0
  25. data/lib/osso/graphql/mutations/create_enterprise_account.rb +0 -7
  26. data/lib/osso/graphql/mutations/create_identity_provider.rb +7 -6
  27. data/lib/osso/graphql/mutations/delete_identity_provider.rb +24 -0
  28. data/lib/osso/graphql/mutations/invite_admin_user.rb +43 -0
  29. data/lib/osso/graphql/query.rb +8 -0
  30. data/lib/osso/graphql/resolvers/enterprise_accounts.rb +3 -3
  31. data/lib/osso/graphql/types.rb +2 -2
  32. data/lib/osso/graphql/types/admin_user.rb +9 -0
  33. data/lib/osso/graphql/types/base_object.rb +1 -1
  34. data/lib/osso/graphql/types/enterprise_account.rb +1 -0
  35. data/lib/osso/graphql/types/identity_provider.rb +2 -0
  36. data/lib/osso/graphql/types/identity_provider_service.rb +3 -1
  37. data/lib/osso/lib/app_config.rb +1 -1
  38. data/lib/osso/lib/route_map.rb +0 -16
  39. data/lib/osso/lib/saml_handler.rb +5 -0
  40. data/lib/osso/models/access_token.rb +4 -2
  41. data/lib/osso/models/account.rb +34 -0
  42. data/lib/osso/models/authorization_code.rb +2 -1
  43. data/lib/osso/models/enterprise_account.rb +3 -1
  44. data/lib/osso/models/identity_provider.rb +19 -5
  45. data/lib/osso/models/models.rb +1 -0
  46. data/lib/osso/models/oauth_client.rb +0 -1
  47. data/lib/osso/models/user.rb +2 -2
  48. data/lib/osso/routes/admin.rb +39 -33
  49. data/lib/osso/routes/auth.rb +9 -9
  50. data/lib/osso/routes/oauth.rb +35 -17
  51. data/lib/osso/version.rb +1 -1
  52. data/lib/osso/views/admin.erb +5 -0
  53. data/lib/osso/views/error.erb +1 -0
  54. data/lib/osso/views/layout.erb +0 -0
  55. data/lib/osso/views/multiple_providers.erb +1 -0
  56. data/lib/osso/views/welcome.erb +0 -0
  57. data/lib/tasks/bootstrap.rake +25 -4
  58. data/osso-rb.gemspec +5 -0
  59. data/spec/factories/account.rb +24 -0
  60. data/spec/factories/enterprise_account.rb +11 -3
  61. data/spec/factories/identity_providers.rb +10 -2
  62. data/spec/factories/user.rb +4 -0
  63. data/spec/graphql/mutations/configure_identity_provider_spec.rb +1 -1
  64. data/spec/graphql/mutations/create_enterprise_account_spec.rb +0 -14
  65. data/spec/graphql/mutations/create_identity_provider_spec.rb +59 -8
  66. data/spec/graphql/query/identity_provider_spec.rb +2 -2
  67. data/spec/models/enterprise_account_spec.rb +18 -0
  68. data/spec/models/identity_provider_spec.rb +24 -3
  69. data/spec/routes/admin_spec.rb +7 -41
  70. data/spec/routes/auth_spec.rb +17 -18
  71. data/spec/routes/oauth_spec.rb +88 -5
  72. data/spec/spec_helper.rb +3 -3
  73. data/spec/support/views/layout.erb +1 -0
  74. data/spec/support/views/multiple_providers.erb +1 -0
  75. metadata +107 -7
  76. data/lib/osso/helpers/auth.rb +0 -94
  77. data/lib/osso/helpers/helpers.rb +0 -8
  78. data/spec/helpers/auth_spec.rb +0 -269
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Osso
4
4
  require_relative 'osso/error/error'
5
- require_relative 'osso/helpers/helpers'
6
5
  require_relative 'osso/lib/app_config'
7
6
  require_relative 'osso/lib/oauth2_token'
8
7
  require_relative 'osso/lib/route_map'
@@ -0,0 +1,6 @@
1
+ class AddUsersCountToIdentityProvidersAndEnterpriseAccounts < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :enterprise_accounts, :users_count, :integer, default: 0
4
+ add_column :identity_providers, :users_count, :integer, default: 0
5
+ end
6
+ end
@@ -0,0 +1,47 @@
1
+ require 'rodauth/migrations'
2
+
3
+ class AddRodauthTables < ActiveRecord::Migration[6.0]
4
+ DB = Sequel.postgres(extensions: :activerecord_connection)
5
+
6
+ def change
7
+ enable_extension "citext"
8
+
9
+ create_table :accounts, id: :uuid do |t|
10
+ t.citext :email, null: false, index: { unique: true, where: "status_id IN (1, 2)" }
11
+ t.integer :status_id, null: false, default: 1
12
+ t.string :role, null: false, default: 'admin'
13
+ t.string :oauth_client_id, null: true, index: true
14
+ end
15
+
16
+ create_table :account_password_hashes, id: :uuid do |t|
17
+ t.foreign_key :accounts, column: :id
18
+ t.string :password_hash, null: false
19
+ end
20
+
21
+ Rodauth.create_database_authentication_functions(DB, table_name: "account_password_hashes")
22
+
23
+ # Used by the password reset feature
24
+ create_table :account_password_reset_keys, id: :uuid do |t|
25
+ t.foreign_key :accounts, column: :id
26
+ t.string :key, null: false
27
+ t.datetime :deadline, null: false
28
+ t.datetime :email_last_sent, null: false, default: -> { "CURRENT_TIMESTAMP" }
29
+ end
30
+
31
+ # Used by the account verification feature
32
+ create_table :account_verification_keys, id: :uuid do |t|
33
+ t.string :key, null: false
34
+ t.datetime :requested_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
35
+ t.datetime :email_last_sent, null: false, default: -> { "CURRENT_TIMESTAMP" }
36
+ end
37
+
38
+ add_reference :account_verification_keys, :account, type: :uuid, index: true
39
+
40
+ # Used by the remember me feature
41
+ create_table :account_remember_keys, id: :uuid do |t|
42
+ t.foreign_key :accounts, column: :id
43
+ t.string :key, null: false
44
+ t.datetime :deadline, null: false
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,5 @@
1
+ class AddTokenIndexToAccessTokens < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_index :access_tokens, [:token, :expires_at], unique: true
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddRequestedToAuthorizationCodesAndAccessTokens < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :access_tokens, :requested, :jsonb, default: {}
4
+ add_column :authorization_codes, :requested, :jsonb, default: {}
5
+ end
6
+ end
@@ -0,0 +1,12 @@
1
+ class AddSsoIssuerToIdentityProviders < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :identity_providers, :sso_issuer, :string
4
+
5
+ Osso::Models::IdentityProvider.all.each do |idp|
6
+ idp.sso_issuer = idp.root_url + "/" + idp.domain
7
+ idp.save
8
+ end
9
+
10
+ change_column_null :identity_providers, :sso_issuer, false
11
+ end
12
+ end
@@ -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
@@ -0,0 +1,28 @@
1
+ class AddSalesforceToProviderServiceEnum < ActiveRecord::Migration[6.0]
2
+ disable_ddl_transaction!
3
+
4
+ def up
5
+ execute <<-SQL
6
+ ALTER TYPE identity_provider_service ADD VALUE 'SALESFORCE';
7
+ SQL
8
+ end
9
+
10
+ def down
11
+ execute <<~SQL
12
+ CREATE TYPE identity_provider_service_new AS ENUM ('AZURE', 'OKTA', 'ONELOGIN', 'GOOGLE', 'PING');
13
+
14
+ -- Remove values that won't be compatible with new definition
15
+ DELETE FROM identity_providers WHERE service = 'SALESFORCE';
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
@@ -11,7 +11,9 @@ module Osso
11
11
  field :create_enterprise_account, mutation: Mutations::CreateEnterpriseAccount
12
12
  field :create_oauth_client, mutation: Mutations::CreateOauthClient
13
13
  field :delete_enterprise_account, mutation: Mutations::DeleteEnterpriseAccount
14
+ field :delete_identity_provider, mutation: Mutations::DeleteIdentityProvider
14
15
  field :delete_oauth_client, mutation: Mutations::DeleteOauthClient
16
+ field :invite_admin_user, mutation: Mutations::InviteAdminUser
15
17
  field :set_redirect_uris, mutation: Mutations::SetRedirectUris
16
18
  field :regenerate_oauth_credentials, mutation: Mutations::RegenerateOauthCredentials
17
19
  field :update_app_config, mutation: Mutations::UpdateAppConfig
@@ -11,7 +11,9 @@ require_relative 'mutations/create_identity_provider'
11
11
  require_relative 'mutations/create_enterprise_account'
12
12
  require_relative 'mutations/create_oauth_client'
13
13
  require_relative 'mutations/delete_enterprise_account'
14
+ require_relative 'mutations/delete_identity_provider'
14
15
  require_relative 'mutations/delete_oauth_client'
16
+ require_relative 'mutations/invite_admin_user'
15
17
  require_relative 'mutations/regenerate_oauth_credentials'
16
18
  require_relative 'mutations/set_redirect_uris'
17
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,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Osso
4
+ module GraphQL
5
+ module Mutations
6
+ class DeleteIdentityProvider < BaseMutation
7
+ null false
8
+
9
+ argument :id, ID, required: true
10
+
11
+ field :identity_provider, Types::IdentityProvider, null: true
12
+ field :errors, [String], null: false
13
+
14
+ def resolve(id:)
15
+ identity_provider = Osso::Models::IdentityProvider.find(id)
16
+
17
+ return response_data(identity_provider: nil) if identity_provider.destroy
18
+
19
+ response_error(identity_provider.errors)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ 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,12 +6,12 @@ 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
-
14
- accounts = accounts.order(sort_column => sort_order_sym(sort_order)) if sort_column && sort_order
13
+ accounts = accounts.where('domain ilike ? OR name ilike ?', "%#{search}%", "%#{search}%") if search
14
+ accounts = accounts.order(sort_column.underscore => sort_order_sym(sort_order)) if sort_column && sort_order
15
15
 
16
16
  accounts.all
17
17
  end
@@ -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
@@ -14,6 +14,7 @@ module Osso
14
14
  field :domain, String, null: false
15
15
  field :identity_providers, [Types::IdentityProvider], null: true
16
16
  field :status, String, null: false
17
+ field :users_count, Integer, null: false
17
18
 
18
19
  def status
19
20
  'active'
@@ -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