osso 0.0.5.pre.lambda → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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
  #