osso 0.0.5.pre.zeta → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) 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 +69 -51
  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 +3 -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 -15
  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 +24 -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 +42 -18
  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 +3 -2
  67. data/spec/models/enterprise_account_spec.rb +18 -0
  68. data/spec/models/identity_provider_spec.rb +36 -1
  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 +102 -5
  72. data/spec/spec_helper.rb +3 -3
  73. data/spec/support/views/hosted_login.erb +1 -0
  74. data/spec/support/views/layout.erb +1 -0
  75. data/spec/support/views/multiple_providers.erb +1 -0
  76. metadata +108 -7
  77. data/lib/osso/helpers/auth.rb +0 -94
  78. data/lib/osso/helpers/helpers.rb +0 -8
  79. data/spec/helpers/auth_spec.rb +0 -97
@@ -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,9 +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
20
+ field :acs_url_validator, String, null: false
21
+ field :oauth_client, Types::OauthClient, null: false
19
22
  end
20
23
  end
21
24
  end