scimitar 1.0.0

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 (106) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +16 -0
  3. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +180 -0
  4. data/app/controllers/scimitar/application_controller.rb +129 -0
  5. data/app/controllers/scimitar/resource_types_controller.rb +28 -0
  6. data/app/controllers/scimitar/resources_controller.rb +203 -0
  7. data/app/controllers/scimitar/schemas_controller.rb +16 -0
  8. data/app/controllers/scimitar/service_provider_configurations_controller.rb +8 -0
  9. data/app/models/scimitar/authentication_error.rb +9 -0
  10. data/app/models/scimitar/authentication_scheme.rb +18 -0
  11. data/app/models/scimitar/bulk.rb +8 -0
  12. data/app/models/scimitar/complex_types/address.rb +18 -0
  13. data/app/models/scimitar/complex_types/base.rb +41 -0
  14. data/app/models/scimitar/complex_types/email.rb +12 -0
  15. data/app/models/scimitar/complex_types/entitlement.rb +12 -0
  16. data/app/models/scimitar/complex_types/ims.rb +12 -0
  17. data/app/models/scimitar/complex_types/name.rb +12 -0
  18. data/app/models/scimitar/complex_types/phone_number.rb +12 -0
  19. data/app/models/scimitar/complex_types/photo.rb +12 -0
  20. data/app/models/scimitar/complex_types/reference_group.rb +12 -0
  21. data/app/models/scimitar/complex_types/reference_member.rb +12 -0
  22. data/app/models/scimitar/complex_types/role.rb +12 -0
  23. data/app/models/scimitar/complex_types/x509_certificate.rb +12 -0
  24. data/app/models/scimitar/engine_configuration.rb +24 -0
  25. data/app/models/scimitar/error_response.rb +20 -0
  26. data/app/models/scimitar/errors.rb +14 -0
  27. data/app/models/scimitar/filter.rb +11 -0
  28. data/app/models/scimitar/filter_error.rb +22 -0
  29. data/app/models/scimitar/invalid_syntax_error.rb +9 -0
  30. data/app/models/scimitar/lists/count.rb +64 -0
  31. data/app/models/scimitar/lists/query_parser.rb +730 -0
  32. data/app/models/scimitar/meta.rb +7 -0
  33. data/app/models/scimitar/not_found_error.rb +10 -0
  34. data/app/models/scimitar/resource_invalid_error.rb +9 -0
  35. data/app/models/scimitar/resource_type.rb +29 -0
  36. data/app/models/scimitar/resources/base.rb +159 -0
  37. data/app/models/scimitar/resources/group.rb +13 -0
  38. data/app/models/scimitar/resources/mixin.rb +964 -0
  39. data/app/models/scimitar/resources/user.rb +13 -0
  40. data/app/models/scimitar/schema/address.rb +24 -0
  41. data/app/models/scimitar/schema/attribute.rb +123 -0
  42. data/app/models/scimitar/schema/base.rb +86 -0
  43. data/app/models/scimitar/schema/derived_attributes.rb +24 -0
  44. data/app/models/scimitar/schema/email.rb +10 -0
  45. data/app/models/scimitar/schema/entitlement.rb +10 -0
  46. data/app/models/scimitar/schema/group.rb +27 -0
  47. data/app/models/scimitar/schema/ims.rb +10 -0
  48. data/app/models/scimitar/schema/name.rb +20 -0
  49. data/app/models/scimitar/schema/phone_number.rb +10 -0
  50. data/app/models/scimitar/schema/photo.rb +10 -0
  51. data/app/models/scimitar/schema/reference_group.rb +23 -0
  52. data/app/models/scimitar/schema/reference_member.rb +21 -0
  53. data/app/models/scimitar/schema/role.rb +10 -0
  54. data/app/models/scimitar/schema/user.rb +52 -0
  55. data/app/models/scimitar/schema/vdtp.rb +18 -0
  56. data/app/models/scimitar/schema/x509_certificate.rb +22 -0
  57. data/app/models/scimitar/service_provider_configuration.rb +49 -0
  58. data/app/models/scimitar/supportable.rb +14 -0
  59. data/app/views/layouts/scimitar/application.html.erb +14 -0
  60. data/config/initializers/scimitar.rb +82 -0
  61. data/config/routes.rb +6 -0
  62. data/lib/scimitar.rb +23 -0
  63. data/lib/scimitar/engine.rb +63 -0
  64. data/lib/scimitar/version.rb +13 -0
  65. data/spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb +24 -0
  66. data/spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb +30 -0
  67. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +13 -0
  68. data/spec/apps/dummy/app/controllers/mock_users_controller.rb +13 -0
  69. data/spec/apps/dummy/app/models/mock_group.rb +83 -0
  70. data/spec/apps/dummy/app/models/mock_user.rb +104 -0
  71. data/spec/apps/dummy/config/application.rb +17 -0
  72. data/spec/apps/dummy/config/boot.rb +2 -0
  73. data/spec/apps/dummy/config/environment.rb +2 -0
  74. data/spec/apps/dummy/config/environments/test.rb +15 -0
  75. data/spec/apps/dummy/config/initializers/cookies_serializer.rb +3 -0
  76. data/spec/apps/dummy/config/initializers/scimitar.rb +14 -0
  77. data/spec/apps/dummy/config/initializers/session_store.rb +3 -0
  78. data/spec/apps/dummy/config/routes.rb +24 -0
  79. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +15 -0
  80. data/spec/apps/dummy/db/migrate/20210308020313_create_mock_groups.rb +10 -0
  81. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +8 -0
  82. data/spec/apps/dummy/db/schema.rb +42 -0
  83. data/spec/controllers/scimitar/application_controller_spec.rb +173 -0
  84. data/spec/controllers/scimitar/resource_types_controller_spec.rb +94 -0
  85. data/spec/controllers/scimitar/resources_controller_spec.rb +247 -0
  86. data/spec/controllers/scimitar/schemas_controller_spec.rb +75 -0
  87. data/spec/controllers/scimitar/service_provider_configurations_controller_spec.rb +22 -0
  88. data/spec/models/scimitar/complex_types/address_spec.rb +19 -0
  89. data/spec/models/scimitar/complex_types/email_spec.rb +23 -0
  90. data/spec/models/scimitar/lists/count_spec.rb +147 -0
  91. data/spec/models/scimitar/lists/query_parser_spec.rb +763 -0
  92. data/spec/models/scimitar/resource_type_spec.rb +21 -0
  93. data/spec/models/scimitar/resources/base_spec.rb +289 -0
  94. data/spec/models/scimitar/resources/base_validation_spec.rb +61 -0
  95. data/spec/models/scimitar/resources/mixin_spec.rb +2127 -0
  96. data/spec/models/scimitar/resources/user_spec.rb +55 -0
  97. data/spec/models/scimitar/schema/attribute_spec.rb +80 -0
  98. data/spec/models/scimitar/schema/base_spec.rb +64 -0
  99. data/spec/models/scimitar/schema/group_spec.rb +87 -0
  100. data/spec/models/scimitar/schema/user_spec.rb +710 -0
  101. data/spec/requests/active_record_backed_resources_controller_spec.rb +569 -0
  102. data/spec/requests/application_controller_spec.rb +49 -0
  103. data/spec/requests/controller_configuration_spec.rb +17 -0
  104. data/spec/requests/engine_spec.rb +20 -0
  105. data/spec/spec_helper.rb +66 -0
  106. metadata +315 -0
@@ -0,0 +1,52 @@
1
+ module Scimitar
2
+ module Schema
3
+ # Represents the schema for the User resource
4
+ # See also Scimitar::Resources::User
5
+ class User < Base
6
+
7
+ def initialize(options = {})
8
+ super(name: 'User',
9
+ id: self.class.id,
10
+ description: 'Represents a User',
11
+ scim_attributes: self.class.scim_attributes)
12
+
13
+ end
14
+
15
+ def self.id
16
+ 'urn:ietf:params:scim:schemas:core:2.0:User'
17
+ end
18
+
19
+ def self.scim_attributes
20
+ [
21
+ Attribute.new(name: 'userName', type: 'string', uniqueness: 'server', required: true),
22
+
23
+ Attribute.new(name: 'name', complexType: Scimitar::ComplexTypes::Name),
24
+
25
+ Attribute.new(name: 'displayName', type: 'string'),
26
+ Attribute.new(name: 'nickName', type: 'string'),
27
+ Attribute.new(name: 'profileUrl', type: 'string'),
28
+ Attribute.new(name: 'title', type: 'string'),
29
+ Attribute.new(name: 'userType', type: 'string'),
30
+ Attribute.new(name: 'preferredLanguage', type: 'string'),
31
+ Attribute.new(name: 'locale', type: 'string'),
32
+ Attribute.new(name: 'timezone', type: 'string'),
33
+
34
+ Attribute.new(name: 'active', type: 'boolean'),
35
+
36
+ Attribute.new(name: 'password', type: 'string', mutability: 'writeOnly', returned: 'never'),
37
+
38
+ Attribute.new(name: 'emails', multiValued: true, complexType: Scimitar::ComplexTypes::Email),
39
+ Attribute.new(name: 'phoneNumbers', multiValued: true, complexType: Scimitar::ComplexTypes::PhoneNumber),
40
+ Attribute.new(name: 'ims', multiValued: true, complexType: Scimitar::ComplexTypes::Ims),
41
+ Attribute.new(name: 'photos', multiValued: true, complexType: Scimitar::ComplexTypes::Photo),
42
+ Attribute.new(name: 'addresses', multiValued: true, complexType: Scimitar::ComplexTypes::Address),
43
+ Attribute.new(name: 'groups', multiValued: true, complexType: Scimitar::ComplexTypes::ReferenceGroup, mutability: 'readOnly'),
44
+ Attribute.new(name: 'entitlements', multiValued: true, complexType: Scimitar::ComplexTypes::Entitlement),
45
+ Attribute.new(name: 'roles', multiValued: true, complexType: Scimitar::ComplexTypes::Role),
46
+ Attribute.new(name: 'x509Certificates', multiValued: true, complexType: Scimitar::ComplexTypes::X509Certificate),
47
+ ]
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,18 @@
1
+ module Scimitar
2
+ module Schema
3
+
4
+ # Represents a common schema for a few complex types; base class DRYs up
5
+ # code. "Vdtp" - Value, Display, Type, Primary.
6
+ #
7
+ class Vdtp < Base
8
+ def self.scim_attributes
9
+ @scim_attributes ||= [
10
+ Attribute.new(name: 'value', type: 'string', required: true),
11
+ Attribute.new(name: 'display', type: 'string', mutability: 'readOnly'),
12
+ Attribute.new(name: 'type', type: 'string'),
13
+ Attribute.new(name: 'primary', type: 'boolean'),
14
+ ]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ module Scimitar
2
+ module Schema
3
+
4
+ # Represents the schema for the X509Certificate complex type.
5
+ # The 'value' holds the certificate data.
6
+ #
7
+ # Similar to the Vdtp class, but the "value" field is of type "binary".
8
+ #
9
+ # See also Scimitar::ComplexTypes::X509Certificate
10
+ #
11
+ class X509Certificate < Base
12
+ def self.scim_attributes
13
+ @scim_attributes ||= [
14
+ Attribute.new(name: 'value', type: 'binary', required: true),
15
+ Attribute.new(name: 'display', type: 'string', mutability: 'readOnly'),
16
+ Attribute.new(name: 'type', type: 'string'),
17
+ Attribute.new(name: 'primary', type: 'boolean'),
18
+ ]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,49 @@
1
+ module Scimitar
2
+
3
+ # Represents the service provider info. Used by the /ServiceProviderConfig
4
+ # endpoint to provide specification compliance, authentication schemes and
5
+ # data models. Renders to JSON as a SCIM ServiceProviderConfig type.
6
+ #
7
+ # See config/initializers/scimitar.rb for more information.
8
+ #
9
+ class ServiceProviderConfiguration
10
+ include ActiveModel::Model
11
+
12
+ attr_accessor :patch, :bulk, :filter, :changePassword,
13
+ :sort, :etag, :authenticationSchemes,
14
+ :schemas, :meta
15
+
16
+ def initialize(attributes = {})
17
+ defaults = {
18
+ bulk: Supportable.unsupported,
19
+ changePassword: Supportable.unsupported,
20
+ sort: Supportable.unsupported,
21
+ etag: Supportable.unsupported,
22
+
23
+ patch: Supportable.supported,
24
+
25
+ filter: Scimitar::Filter.new(
26
+ supported: true,
27
+ maxResults: Scimitar::Filter::MAX_RESULTS_DEFAULT
28
+ ),
29
+
30
+ schemas: ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
31
+
32
+ meta: Meta.new(
33
+ resourceType: 'ServiceProviderConfig',
34
+ created: Time.now,
35
+ lastModified: Time.now,
36
+ version: '1'
37
+ ),
38
+
39
+ authenticationSchemes: [
40
+ AuthenticationScheme.basic,
41
+ AuthenticationScheme.bearer
42
+ ]
43
+ }
44
+
45
+ super(defaults.merge(attributes))
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,14 @@
1
+ module Scimitar
2
+ class Supportable
3
+ include ActiveModel::Model
4
+ attr_accessor :supported
5
+
6
+ def self.supported
7
+ new(supported: true)
8
+ end
9
+
10
+ def self.unsupported
11
+ new(supported: false)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Scimitar</title>
5
+ <%= stylesheet_link_tag "scimitar/application", media: "all" %>
6
+ <%= javascript_include_tag "scimitar/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,82 @@
1
+ # SCIMITAR CONFIGURATION
2
+ #
3
+ # For supporting information and rationale, please see README.md.
4
+
5
+ # =============================================================================
6
+ # SERVICE PROVIDER CONFIGURATION
7
+ # =============================================================================
8
+ #
9
+ # This is a Ruby abstraction over a SCIM entity that declares the capabilities
10
+ # supported by a particular implementation.
11
+ #
12
+ # Typically this is used to declare parts of the standard unsupported, if you
13
+ # don't need them and don't want to provide subclass support.
14
+ #
15
+ Scimitar.service_provider_configuration = Scimitar::ServiceProviderConfiguration.new({
16
+
17
+ # See https://tools.ietf.org/html/rfc7643#section-8.5 for properties.
18
+ #
19
+ # See Gem source file 'app/models/scimitar/service_provider_configuration.rb'
20
+ # for defaults. Define Hash keys here that override defaults; e.g. to declare
21
+ # that filters are not supported so that calling clients shouldn't use them:
22
+ #
23
+ # filter: Scimitar::Supported.unsupported
24
+
25
+ })
26
+
27
+ # =============================================================================
28
+ # ENGINE CONFIGURATION
29
+ # =============================================================================
30
+ #
31
+ # This is where you provide callbacks for things like authorisation or mixins
32
+ # that get included into all Scimitar-derived controllers (for things like
33
+ # before-actions that apply to all Scimitar controller-based routes).
34
+ #
35
+ Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
36
+
37
+ # If you have filters you want to run for any Scimitar action/route, you can
38
+ # define them here. For example, you might use a before-action to set up some
39
+ # multi-tenancy related state, or skip Rails CSRF token verification/
40
+ #
41
+ # For example:
42
+ #
43
+ # application_controller_mixin: Module.new do
44
+ # def self.included(base)
45
+ # base.class_eval do
46
+ #
47
+ # # Anything here is written just as you'd write it at the top of
48
+ # # one of your controller classes, but it gets included in all
49
+ # # Scimitar classes too.
50
+ #
51
+ # skip_before_action :verify_authenticity_token
52
+ # prepend_before_action :setup_some_kind_of_multi_tenancy_data
53
+ # end
54
+ # end
55
+ # end, # ...other configuration entries might follow...
56
+
57
+ # If you want to support username/password authentication:
58
+ #
59
+ # basic_authenticator: Proc.new do | username, password |
60
+ # # Check username/password and return 'true' if valid, else 'false'.
61
+ # end, # ...other configuration entries might follow...
62
+ #
63
+ # The 'username' and 'password' parameters come from Rails:
64
+ #
65
+ # https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Basic.html
66
+ # https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Basic/ControllerMethods.html#method-i-authenticate_with_http_basic
67
+
68
+ # If you want to support HTTP bearer token (OAuth-style) authentication:
69
+ #
70
+ # token_authenticator: Proc.new do | token, options |
71
+ # # Check token and return 'true' if valid, else 'false'.
72
+ # end, # ...other configuration entries might follow...
73
+ #
74
+ # The 'token' and 'options' parameters come from Rails:
75
+ #
76
+ # https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token.html
77
+ # https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token/ControllerMethods.html#method-i-authenticate_with_http_token
78
+ #
79
+ # Note that both basic and token authentication can be declared, with the
80
+ # parameters in the inbound HTTP request determining which is invoked.
81
+
82
+ })
data/config/routes.rb ADDED
@@ -0,0 +1,6 @@
1
+ Scimitar::Engine.routes.draw do
2
+ get 'ServiceProviderConfig', to: 'service_provider_configurations#show', as: :scim_service_provider_configuration
3
+ get 'ResourceTypes', to: 'resource_types#index', as: :scim_resource_types
4
+ get 'ResourceTypes/:name', to: 'resource_types#show', as: :scim_resource_type
5
+ get 'Schemas', to: 'schemas#index', as: :scim_schemas
6
+ end
data/lib/scimitar.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'scimitar/version'
2
+ require 'scimitar/engine'
3
+
4
+ module Scimitar
5
+ def self.service_provider_configuration=(custom_configuration)
6
+ @service_provider_configuration = custom_configuration
7
+ end
8
+
9
+ def self.service_provider_configuration(location:)
10
+ @service_provider_configuration ||= ServiceProviderConfiguration.new
11
+ @service_provider_configuration.meta.location = location
12
+ @service_provider_configuration
13
+ end
14
+
15
+ def self.engine_configuration=(custom_configuration)
16
+ @engine_configuration = custom_configuration
17
+ end
18
+
19
+ def self.engine_configuration
20
+ @engine_configuration ||= EngineConfiguration.new
21
+ @engine_configuration
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ module Scimitar
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Scimitar
4
+
5
+ Mime::Type.register 'application/scim+json', :scim
6
+
7
+ ActionDispatch::Request.parameter_parsers[Mime::Type.lookup('application/scim+json').symbol] = lambda do |body|
8
+ JSON.parse(body)
9
+ end
10
+
11
+ def self.resources
12
+ default_resources + custom_resources
13
+ end
14
+
15
+ # Can be used to add a new resource type which is not provided by the gem.
16
+ # For example:
17
+ #
18
+ # module Scim
19
+ # module Resources
20
+ # class ShinyResource < Scimitar::Resources::Base
21
+ # set_schema Scim::Schema::Shiny
22
+ #
23
+ # def self.endpoint
24
+ # "/Shinies"
25
+ # end
26
+ # end
27
+ # end
28
+ # end
29
+ #
30
+ # Scimitar::Engine.add_custom_resource Scim::Resources::ShinyResource
31
+ #
32
+ def self.add_custom_resource(resource)
33
+ custom_resources << resource
34
+ end
35
+
36
+ # Resets the resource list to default. This is really only intended for use
37
+ # during testing, to avoid one test polluting another.
38
+ #
39
+ def self.reset_custom_resources
40
+ @custom_resources = []
41
+ end
42
+
43
+ # Returns the list of custom resources, if any.
44
+ #
45
+ def self.custom_resources
46
+ @custom_resources ||= []
47
+ end
48
+
49
+ # Returns the default resources added in this gem:
50
+ #
51
+ # * Scimitar::Resources::User
52
+ # * Scimitar::Resources::Group
53
+ #
54
+ def self.default_resources
55
+ [ Resources::User, Resources::Group ]
56
+ end
57
+
58
+ def self.schemas
59
+ resources.map(&:schemas).flatten.uniq.map(&:new)
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,13 @@
1
+ module Scimitar
2
+
3
+ # Gem version. If this changes, be sure to re-run "bundle install" or
4
+ # "bundle update".
5
+ #
6
+ VERSION = '1.0.0'
7
+
8
+ # Date for VERSION. If this changes, be sure to re-run "bundle install"
9
+ # or "bundle update".
10
+ #
11
+ DATE = '2021-03-24'
12
+
13
+ end
@@ -0,0 +1,24 @@
1
+ # For tests only - uses custom 'destroy' implementation which passes a block to
2
+ # Scimitar::ActiveRecordBackedResourcesController#destroy.
3
+ #
4
+ class CustomDestroyMockUsersController < Scimitar::ActiveRecordBackedResourcesController
5
+
6
+ NOT_REALLY_DELETED_USERNAME_INDICATOR = 'not really deleted'
7
+
8
+ def destroy
9
+ super do | resource |
10
+ resource.update!(username: NOT_REALLY_DELETED_USERNAME_INDICATOR)
11
+ end
12
+ end
13
+
14
+ protected
15
+
16
+ def storage_class
17
+ MockUser
18
+ end
19
+
20
+ def storage_scope
21
+ MockUser.all
22
+ end
23
+
24
+ end
@@ -0,0 +1,30 @@
1
+ # For tests only - uses custom 'index' implementation which returns information
2
+ # from the Rails 'request' object in its response.
3
+ #
4
+ class CustomRequestVerifiersController < Scimitar::ActiveRecordBackedResourcesController
5
+
6
+ def index
7
+ render json: {
8
+ request: {
9
+ is_scim: request.format == :scim,
10
+ format: request.format.to_s,
11
+ content_type: request.headers['CONTENT_TYPE']
12
+ }
13
+ }
14
+ end
15
+
16
+ def create
17
+ # Used for invalid JSON input tests
18
+ end
19
+
20
+ protected
21
+
22
+ def storage_class
23
+ MockUser
24
+ end
25
+
26
+ def storage_scope
27
+ MockUser.all
28
+ end
29
+
30
+ end
@@ -0,0 +1,13 @@
1
+ class MockUsersController < Scimitar::ActiveRecordBackedResourcesController
2
+
3
+ protected
4
+
5
+ def storage_class
6
+ MockGroup
7
+ end
8
+
9
+ def storage_scope
10
+ MockGroup.all
11
+ end
12
+
13
+ end