powerhome-scimitar 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +708 -0
  4. data/Rakefile +16 -0
  5. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +257 -0
  6. data/app/controllers/scimitar/application_controller.rb +157 -0
  7. data/app/controllers/scimitar/resource_types_controller.rb +28 -0
  8. data/app/controllers/scimitar/resources_controller.rb +203 -0
  9. data/app/controllers/scimitar/schemas_controller.rb +21 -0
  10. data/app/controllers/scimitar/service_provider_configurations_controller.rb +8 -0
  11. data/app/models/scimitar/authentication_error.rb +9 -0
  12. data/app/models/scimitar/authentication_scheme.rb +18 -0
  13. data/app/models/scimitar/bulk.rb +8 -0
  14. data/app/models/scimitar/complex_types/address.rb +12 -0
  15. data/app/models/scimitar/complex_types/base.rb +83 -0
  16. data/app/models/scimitar/complex_types/email.rb +12 -0
  17. data/app/models/scimitar/complex_types/entitlement.rb +12 -0
  18. data/app/models/scimitar/complex_types/ims.rb +12 -0
  19. data/app/models/scimitar/complex_types/name.rb +12 -0
  20. data/app/models/scimitar/complex_types/phone_number.rb +12 -0
  21. data/app/models/scimitar/complex_types/photo.rb +12 -0
  22. data/app/models/scimitar/complex_types/reference_group.rb +12 -0
  23. data/app/models/scimitar/complex_types/reference_member.rb +12 -0
  24. data/app/models/scimitar/complex_types/role.rb +12 -0
  25. data/app/models/scimitar/complex_types/x509_certificate.rb +12 -0
  26. data/app/models/scimitar/engine_configuration.rb +32 -0
  27. data/app/models/scimitar/error_response.rb +32 -0
  28. data/app/models/scimitar/errors.rb +14 -0
  29. data/app/models/scimitar/filter.rb +11 -0
  30. data/app/models/scimitar/filter_error.rb +22 -0
  31. data/app/models/scimitar/invalid_syntax_error.rb +9 -0
  32. data/app/models/scimitar/lists/count.rb +64 -0
  33. data/app/models/scimitar/lists/query_parser.rb +745 -0
  34. data/app/models/scimitar/meta.rb +7 -0
  35. data/app/models/scimitar/not_found_error.rb +10 -0
  36. data/app/models/scimitar/resource_invalid_error.rb +9 -0
  37. data/app/models/scimitar/resource_type.rb +29 -0
  38. data/app/models/scimitar/resources/base.rb +190 -0
  39. data/app/models/scimitar/resources/group.rb +13 -0
  40. data/app/models/scimitar/resources/mixin.rb +1524 -0
  41. data/app/models/scimitar/resources/user.rb +13 -0
  42. data/app/models/scimitar/schema/address.rb +25 -0
  43. data/app/models/scimitar/schema/attribute.rb +132 -0
  44. data/app/models/scimitar/schema/base.rb +90 -0
  45. data/app/models/scimitar/schema/derived_attributes.rb +24 -0
  46. data/app/models/scimitar/schema/email.rb +10 -0
  47. data/app/models/scimitar/schema/entitlement.rb +10 -0
  48. data/app/models/scimitar/schema/group.rb +27 -0
  49. data/app/models/scimitar/schema/ims.rb +10 -0
  50. data/app/models/scimitar/schema/name.rb +20 -0
  51. data/app/models/scimitar/schema/phone_number.rb +10 -0
  52. data/app/models/scimitar/schema/photo.rb +10 -0
  53. data/app/models/scimitar/schema/reference_group.rb +23 -0
  54. data/app/models/scimitar/schema/reference_member.rb +21 -0
  55. data/app/models/scimitar/schema/role.rb +10 -0
  56. data/app/models/scimitar/schema/user.rb +52 -0
  57. data/app/models/scimitar/schema/vdtp.rb +18 -0
  58. data/app/models/scimitar/schema/x509_certificate.rb +22 -0
  59. data/app/models/scimitar/service_provider_configuration.rb +60 -0
  60. data/app/models/scimitar/supportable.rb +14 -0
  61. data/app/views/layouts/scimitar/application.html.erb +14 -0
  62. data/config/initializers/scimitar.rb +111 -0
  63. data/config/routes.rb +6 -0
  64. data/lib/scimitar/engine.rb +63 -0
  65. data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +216 -0
  66. data/lib/scimitar/support/utilities.rb +51 -0
  67. data/lib/scimitar/version.rb +13 -0
  68. data/lib/scimitar.rb +29 -0
  69. data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +25 -0
  70. data/spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb +24 -0
  71. data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +25 -0
  72. data/spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb +30 -0
  73. data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +24 -0
  74. data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +25 -0
  75. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +13 -0
  76. data/spec/apps/dummy/app/controllers/mock_users_controller.rb +13 -0
  77. data/spec/apps/dummy/app/models/mock_group.rb +83 -0
  78. data/spec/apps/dummy/app/models/mock_user.rb +132 -0
  79. data/spec/apps/dummy/config/application.rb +18 -0
  80. data/spec/apps/dummy/config/boot.rb +2 -0
  81. data/spec/apps/dummy/config/environment.rb +2 -0
  82. data/spec/apps/dummy/config/environments/test.rb +38 -0
  83. data/spec/apps/dummy/config/initializers/cookies_serializer.rb +3 -0
  84. data/spec/apps/dummy/config/initializers/scimitar.rb +61 -0
  85. data/spec/apps/dummy/config/initializers/session_store.rb +3 -0
  86. data/spec/apps/dummy/config/routes.rb +45 -0
  87. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +24 -0
  88. data/spec/apps/dummy/db/migrate/20210308020313_create_mock_groups.rb +10 -0
  89. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +13 -0
  90. data/spec/apps/dummy/db/schema.rb +48 -0
  91. data/spec/controllers/scimitar/application_controller_spec.rb +296 -0
  92. data/spec/controllers/scimitar/resource_types_controller_spec.rb +94 -0
  93. data/spec/controllers/scimitar/resources_controller_spec.rb +247 -0
  94. data/spec/controllers/scimitar/schemas_controller_spec.rb +83 -0
  95. data/spec/controllers/scimitar/service_provider_configurations_controller_spec.rb +22 -0
  96. data/spec/models/scimitar/complex_types/address_spec.rb +18 -0
  97. data/spec/models/scimitar/complex_types/email_spec.rb +21 -0
  98. data/spec/models/scimitar/lists/count_spec.rb +147 -0
  99. data/spec/models/scimitar/lists/query_parser_spec.rb +830 -0
  100. data/spec/models/scimitar/resource_type_spec.rb +21 -0
  101. data/spec/models/scimitar/resources/base_spec.rb +485 -0
  102. data/spec/models/scimitar/resources/base_validation_spec.rb +86 -0
  103. data/spec/models/scimitar/resources/mixin_spec.rb +3562 -0
  104. data/spec/models/scimitar/resources/user_spec.rb +68 -0
  105. data/spec/models/scimitar/schema/attribute_spec.rb +99 -0
  106. data/spec/models/scimitar/schema/base_spec.rb +64 -0
  107. data/spec/models/scimitar/schema/group_spec.rb +87 -0
  108. data/spec/models/scimitar/schema/user_spec.rb +720 -0
  109. data/spec/requests/active_record_backed_resources_controller_spec.rb +1354 -0
  110. data/spec/requests/application_controller_spec.rb +61 -0
  111. data/spec/requests/controller_configuration_spec.rb +17 -0
  112. data/spec/requests/engine_spec.rb +45 -0
  113. data/spec/spec_helper.rb +101 -0
  114. data/spec/spec_helper_spec.rb +30 -0
  115. data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +169 -0
  116. metadata +321 -0
@@ -0,0 +1,9 @@
1
+ module Scimitar
2
+
3
+ class AuthenticationError < ErrorResponse
4
+ def initialize
5
+ super(status: 401, detail: 'Requires authentication')
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module Scimitar
2
+ class AuthenticationScheme
3
+ include ActiveModel::Model
4
+ attr_accessor :type, :name, :description
5
+
6
+ def self.basic
7
+ new type: 'httpbasic',
8
+ name: 'HTTP Basic',
9
+ description: 'Authentication scheme using the HTTP Basic Standard'
10
+ end
11
+
12
+ def self.bearer
13
+ new type: 'oauthbearertoken',
14
+ name: 'OAuth Bearer Token',
15
+ description: 'Authentication scheme using the OAuth Bearer Token Standard'
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ module Scimitar
2
+
3
+ # This isn't supported yet; the class is a placeholder.
4
+ #
5
+ class Bulk < Supportable
6
+ attr_accessor :maxOperations, :maxPayloadSize
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ module Scimitar
2
+ module ComplexTypes
3
+
4
+ # Represents the complex Address type.
5
+ #
6
+ # See also Scimitar::Schema::Address
7
+ #
8
+ class Address < Base
9
+ set_schema Scimitar::Schema::Address
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,83 @@
1
+ require 'set'
2
+
3
+ module Scimitar
4
+ module ComplexTypes
5
+
6
+ # This class represents complex types that could be used inside SCIM
7
+ # resources. Each complex type must inherit from this class. They also need
8
+ # to have their own schema defined. For example:
9
+ #
10
+ # module Scimitar
11
+ # module ComplexTypes
12
+ # class Email < Base
13
+ # set_schema Scimitar::Schema::Email
14
+ #
15
+ # def as_json(options = {})
16
+ # {'type' => 'work', 'primary' => true}.merge(super(options))
17
+ # end
18
+ # end
19
+ # end
20
+ # end
21
+ #
22
+ class Base
23
+ include ActiveModel::Model
24
+ include Scimitar::Schema::DerivedAttributes
25
+ include Scimitar::Errors
26
+
27
+ # Instantiates with attribute values - see ActiveModel::Model#initialize.
28
+ #
29
+ # Allows case-insensitive attributes given in options, by enumerating all
30
+ # instance methods that exist in the subclass (at the point where this
31
+ # method runs, 'self' is a subclass, unless someone instantiated this
32
+ # base class directly) and subtracting methods in the base class. Usually
33
+ # this leaves just the attribute accessors, with not much extra.
34
+ #
35
+ # Map a normalized case version of those names to the actual method names
36
+ # then for each key in the inbound options, normalize it and see if one
37
+ # of the actual case method names is available. If so, use that instead.
38
+ #
39
+ # Unmapped option keys will most likely have no corresponding writer
40
+ # method in the subclass and NoMethodError will therefore arise.
41
+ #
42
+ def initialize(options={})
43
+ normalized_method_map = HashWithIndifferentAccess.new
44
+ corrected_options = {}
45
+ probable_accessor_methods = self.class.instance_methods - self.class.superclass.instance_methods
46
+
47
+ unless options.empty?
48
+ probable_accessor_methods.each do | method_name |
49
+ next if method_name.end_with?('=')
50
+ normalized_method_map[method_name.downcase] = method_name
51
+ end
52
+
53
+ options.each do | ambiguous_case_name, value |
54
+ normalized_name = ambiguous_case_name.downcase
55
+ corrected_name = normalized_method_map[normalized_name]
56
+
57
+ if corrected_name.nil?
58
+ corrected_options[ambiguous_case_name] = value # Probably will lead to NoMethodError
59
+ else
60
+ corrected_options[corrected_name] = value
61
+ end
62
+ end
63
+
64
+ options = corrected_options
65
+ end
66
+
67
+ super # Calls into ActiveModel::Model
68
+
69
+ @errors = ActiveModel::Errors.new(self)
70
+ end
71
+
72
+ # Converts the object to its SCIM representation, which is always JSON.
73
+ #
74
+ # +options+:: A hash that could provide default values for some of the
75
+ # attributes of this complex type object.
76
+ #
77
+ def as_json(options={})
78
+ options[:except] ||= ['errors']
79
+ super.except(options)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,12 @@
1
+ module Scimitar
2
+ module ComplexTypes
3
+
4
+ # Represents the complex Email type.
5
+ #
6
+ # See also Scimitar::Schema::Email
7
+ #
8
+ class Email < Base
9
+ set_schema Scimitar::Schema::Email
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Scimitar
2
+ module ComplexTypes
3
+
4
+ # Represents the complex Entitlement type.
5
+ #
6
+ # See also Scimitar::Schema::Entitlement
7
+ #
8
+ class Entitlement < Base
9
+ set_schema Scimitar::Schema::Entitlement
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Scimitar
2
+ module ComplexTypes
3
+
4
+ # Represents the complex Instant Messaging type.
5
+ #
6
+ # See also Scimitar::Schema::Ims
7
+ #
8
+ class Ims < Base
9
+ set_schema Scimitar::Schema::Ims
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Scimitar
2
+ module ComplexTypes
3
+
4
+ # Represents the complex Name type.
5
+ #
6
+ # See also Scimitar::Schema::Name
7
+ #
8
+ class Name < Base
9
+ set_schema Scimitar::Schema::Name
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Scimitar
2
+ module ComplexTypes
3
+
4
+ # Represents the complex PhoneNumber type.
5
+ #
6
+ # See also Scimitar::Schema::PhoneNumber
7
+ #
8
+ class PhoneNumber < Base
9
+ set_schema Scimitar::Schema::PhoneNumber
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Scimitar
2
+ module ComplexTypes
3
+
4
+ # Represents the complex Photo type.
5
+ #
6
+ # See also Scimitar::Schema::Photo
7
+ #
8
+ class Photo < Base
9
+ set_schema Scimitar::Schema::Photo
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Scimitar
2
+ module ComplexTypes
3
+
4
+ # Represents the complex reference-a-group type.
5
+ #
6
+ # See also Scimitar::Schema::ReferenceGroup
7
+ #
8
+ class ReferenceGroup < Base
9
+ set_schema Scimitar::Schema::ReferenceGroup
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Scimitar
2
+ module ComplexTypes
3
+
4
+ # Represents the complex reference-a-member type.
5
+ #
6
+ # See also Scimitar::Schema::ReferenceMember
7
+ #
8
+ class ReferenceMember < Base
9
+ set_schema Scimitar::Schema::ReferenceMember
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Scimitar
2
+ module ComplexTypes
3
+
4
+ # Represents the complex Role type.
5
+ #
6
+ # See also Scimitar::Schema::Role
7
+ #
8
+ class Role < Base
9
+ set_schema Scimitar::Schema::Role
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Scimitar
2
+ module ComplexTypes
3
+
4
+ # Represents the complex X509Certificate type.
5
+ #
6
+ # See also Scimitar::Schema::X509Certificate
7
+ #
8
+ class X509Certificate < Base
9
+ set_schema Scimitar::Schema::X509Certificate
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ module Scimitar
2
+
3
+ # Scimitar general configuration.
4
+ #
5
+ # See config/initializers/scimitar.rb for more information.
6
+ #
7
+ class EngineConfiguration
8
+ include ActiveModel::Model
9
+
10
+ attr_accessor(
11
+ :uses_defaults,
12
+ :basic_authenticator,
13
+ :token_authenticator,
14
+ :application_controller_mixin,
15
+ :exception_reporter,
16
+ :optional_value_fields_required,
17
+ )
18
+
19
+ def initialize(attributes = {})
20
+ @uses_defaults = attributes.empty?
21
+
22
+ # Set defaults that may be overridden by the initializer.
23
+ #
24
+ defaults = {
25
+ optional_value_fields_required: true
26
+ }
27
+
28
+ super(defaults.merge(attributes))
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ module Scimitar
2
+ class ErrorResponse < StandardError
3
+ include ActiveModel::Model
4
+
5
+ attr_accessor :status,
6
+ :detail,
7
+ :scimType
8
+
9
+ def as_json(options = {})
10
+ data = {
11
+ 'schemas': ['urn:ietf:params:scim:api:messages:2.0:Error'],
12
+ 'detail': detail,
13
+ 'status': "#{status}"
14
+ }
15
+
16
+ data['scimType'] = scimType if scimType
17
+ data
18
+ end
19
+
20
+ # Originally Scimitar used attribute "detail" for exception text; it was
21
+ # only for JSON responses at the time, but in hindsight was a bad choice.
22
+ # It should have been "message" given inheritance from StandardError, which
23
+ # then works properly with e.g. error reporting services.
24
+ #
25
+ # The "detail" attribute is still present, for backwards compatibility with
26
+ # any client code that might be using this class.
27
+ #
28
+ def message
29
+ self.detail
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,14 @@
1
+ module Scimitar
2
+ module Errors
3
+ def add_errors_from_hash(errors_hash:, prefix: nil)
4
+ errors_hash.each_pair do |key, value|
5
+ new_key = prefix.nil? ? key : "#{prefix}.#{key}".to_sym
6
+ if value.is_a?(Array)
7
+ value.each {|error| errors.add(new_key, error)}
8
+ else
9
+ errors.add(new_key, value)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module Scimitar
2
+
3
+ # Used to configure filters via
4
+ # app/models/scimitar/service_provider_configuration.rb.
5
+ #
6
+ class Filter < Supportable
7
+ MAX_RESULTS_DEFAULT = 100
8
+
9
+ attr_accessor :maxResults
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ module Scimitar
2
+
3
+ # Raised when an invalid query is attempted, either by being malformed or by
4
+ # being unsupported in some way.
5
+ #
6
+ class FilterError < ErrorResponse
7
+ def initialize(message = nil)
8
+ detail = 'The specified filter syntax was invalid, or the specified attribute and filter comparison combination is not supported'
9
+
10
+ if message.present?
11
+ detail << ":\n#{message}"
12
+ end
13
+
14
+ super(
15
+ status: 400,
16
+ scimType: 'invalidFilter',
17
+ detail: detail
18
+ )
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,9 @@
1
+ module Scimitar
2
+ class InvalidSyntaxError < ErrorResponse
3
+
4
+ def initialize(error_message)
5
+ super(status: 400, scimType: 'invalidSyntax', detail: error_message)
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,64 @@
1
+ module Scimitar
2
+ module Lists
3
+ class Count
4
+ include ActiveModel::Model
5
+
6
+ attr_accessor :limit, :start_index, :total
7
+ attr_reader :offset
8
+
9
+ def initialize(*args)
10
+ @limit = 100
11
+ @start_index = 1
12
+
13
+ super(*args)
14
+ end
15
+
16
+ # Set a limit (page size) value.
17
+ #
18
+ # +value+:: Integer value held in a String. Must be >= 1.
19
+ #
20
+ # Raises exceptions if given non-numeric, zero or negative input.
21
+ #
22
+ def limit=(value)
23
+ value = value&.to_s
24
+ return if value.blank? # NOTE EARLY EXIT
25
+
26
+ validate_numericality(value)
27
+ input = value.to_i
28
+ raise if input < 1
29
+ @limit = input
30
+ end
31
+
32
+ # Set a start index (offset) value. Values start at 1. See also #offset.
33
+ #
34
+ # +value+:: Integer value held in a String. Must be >= 1.
35
+ #
36
+ # Raises exceptions if given non-numeric or negative input. Corrects an
37
+ # input value of zero to 1.
38
+ #
39
+ def start_index=(value)
40
+ value = value&.to_s
41
+ return if value.blank? # NOTE EARLY EXIT
42
+
43
+ validate_numericality(value)
44
+ input = value.to_i
45
+ input = 1 if input < 1
46
+ @start_index = input
47
+ end
48
+
49
+ # Read-only accessor that represents #start_index as a zero-based offset,
50
+ # rather than 1-based. This is useful for most storage engines.
51
+ #
52
+ def offset
53
+ start_index - 1
54
+ end
55
+
56
+ private
57
+
58
+ def validate_numericality(input)
59
+ raise unless input.match?(/\A\d+\z/)
60
+ end
61
+
62
+ end
63
+ end
64
+ end