ddr-core 0.2.1

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 (132) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +12 -0
  3. data/README.md +27 -0
  4. data/Rakefile +30 -0
  5. data/app/assets/config/ddr_core_manifest.js +0 -0
  6. data/app/controllers/users/omniauth_callbacks_controller.rb +11 -0
  7. data/app/controllers/users/sessions_controller.rb +15 -0
  8. data/app/models/concerns/ddr/captionable.rb +25 -0
  9. data/app/models/concerns/ddr/describable.rb +108 -0
  10. data/app/models/concerns/ddr/governable.rb +25 -0
  11. data/app/models/concerns/ddr/has_admin_metadata.rb +141 -0
  12. data/app/models/concerns/ddr/has_attachments.rb +10 -0
  13. data/app/models/concerns/ddr/has_children.rb +10 -0
  14. data/app/models/concerns/ddr/has_content.rb +132 -0
  15. data/app/models/concerns/ddr/has_extracted_text.rb +10 -0
  16. data/app/models/concerns/ddr/has_intermediate_file.rb +25 -0
  17. data/app/models/concerns/ddr/has_multires_image.rb +14 -0
  18. data/app/models/concerns/ddr/has_parent.rb +18 -0
  19. data/app/models/concerns/ddr/has_struct_metadata.rb +21 -0
  20. data/app/models/concerns/ddr/has_thumbnail.rb +33 -0
  21. data/app/models/concerns/ddr/solr_document_behavior.rb +429 -0
  22. data/app/models/concerns/ddr/streamable.rb +25 -0
  23. data/app/models/ddr/admin_set.rb +28 -0
  24. data/app/models/ddr/attachment.rb +14 -0
  25. data/app/models/ddr/collection.rb +28 -0
  26. data/app/models/ddr/component.rb +31 -0
  27. data/app/models/ddr/contact.rb +23 -0
  28. data/app/models/ddr/digest.rb +8 -0
  29. data/app/models/ddr/file.rb +40 -0
  30. data/app/models/ddr/item.rb +36 -0
  31. data/app/models/ddr/language.rb +31 -0
  32. data/app/models/ddr/media_type.rb +22 -0
  33. data/app/models/ddr/resource.rb +94 -0
  34. data/app/models/ddr/rights_statement.rb +25 -0
  35. data/app/models/ddr/target.rb +17 -0
  36. data/config/initializers/devise.rb +262 -0
  37. data/config/locales/ddr-core.en.yml +85 -0
  38. data/config/routes.rb +3 -0
  39. data/db/migrate/20141104181418_create_users.rb +34 -0
  40. data/db/migrate/20141107124012_add_columns_to_user.rb +46 -0
  41. data/lib/ddr-core.rb +1 -0
  42. data/lib/ddr/auth.rb +80 -0
  43. data/lib/ddr/auth/ability.rb +18 -0
  44. data/lib/ddr/auth/ability_definitions.rb +26 -0
  45. data/lib/ddr/auth/ability_definitions/admin_set_ability_definitions.rb +9 -0
  46. data/lib/ddr/auth/ability_definitions/alias_ability_definitions.rb +23 -0
  47. data/lib/ddr/auth/ability_definitions/attachment_ability_definitions.rb +13 -0
  48. data/lib/ddr/auth/ability_definitions/collection_ability_definitions.rb +28 -0
  49. data/lib/ddr/auth/ability_definitions/component_ability_definitions.rb +13 -0
  50. data/lib/ddr/auth/ability_definitions/item_ability_definitions.rb +13 -0
  51. data/lib/ddr/auth/ability_definitions/lock_ability_definitions.rb +13 -0
  52. data/lib/ddr/auth/ability_definitions/publication_ability_definitions.rb +16 -0
  53. data/lib/ddr/auth/ability_definitions/role_based_ability_definitions.rb +39 -0
  54. data/lib/ddr/auth/ability_definitions/superuser_ability_definitions.rb +9 -0
  55. data/lib/ddr/auth/ability_factory.rb +10 -0
  56. data/lib/ddr/auth/abstract_ability.rb +48 -0
  57. data/lib/ddr/auth/affiliation.rb +14 -0
  58. data/lib/ddr/auth/affiliation_groups.rb +20 -0
  59. data/lib/ddr/auth/anonymous_ability.rb +7 -0
  60. data/lib/ddr/auth/auth_context.rb +109 -0
  61. data/lib/ddr/auth/auth_context_factory.rb +13 -0
  62. data/lib/ddr/auth/detached_auth_context.rb +19 -0
  63. data/lib/ddr/auth/dynamic_groups.rb +13 -0
  64. data/lib/ddr/auth/effective_permissions.rb +12 -0
  65. data/lib/ddr/auth/effective_roles.rb +9 -0
  66. data/lib/ddr/auth/failure_app.rb +16 -0
  67. data/lib/ddr/auth/group.rb +40 -0
  68. data/lib/ddr/auth/grouper_gateway.rb +70 -0
  69. data/lib/ddr/auth/groups.rb +32 -0
  70. data/lib/ddr/auth/ldap_gateway.rb +74 -0
  71. data/lib/ddr/auth/permissions.rb +18 -0
  72. data/lib/ddr/auth/remote_groups.rb +14 -0
  73. data/lib/ddr/auth/role_based_access_controls_enforcement.rb +56 -0
  74. data/lib/ddr/auth/roles.rb +28 -0
  75. data/lib/ddr/auth/roles/role.rb +121 -0
  76. data/lib/ddr/auth/roles/role_type.rb +23 -0
  77. data/lib/ddr/auth/roles/role_types.rb +52 -0
  78. data/lib/ddr/auth/superuser_ability.rb +7 -0
  79. data/lib/ddr/auth/test_helpers.rb +22 -0
  80. data/lib/ddr/auth/user.rb +54 -0
  81. data/lib/ddr/auth/web_auth_context.rb +29 -0
  82. data/lib/ddr/core.rb +110 -0
  83. data/lib/ddr/core/engine.rb +8 -0
  84. data/lib/ddr/core/version.rb +5 -0
  85. data/lib/ddr/error.rb +16 -0
  86. data/lib/ddr/files.rb +13 -0
  87. data/lib/ddr/fits.rb +189 -0
  88. data/lib/ddr/index.rb +29 -0
  89. data/lib/ddr/index/abstract_query_result.rb +22 -0
  90. data/lib/ddr/index/connection.rb +38 -0
  91. data/lib/ddr/index/csv_query_result.rb +84 -0
  92. data/lib/ddr/index/document_builder.rb +9 -0
  93. data/lib/ddr/index/field.rb +35 -0
  94. data/lib/ddr/index/field_attribute.rb +22 -0
  95. data/lib/ddr/index/fields.rb +154 -0
  96. data/lib/ddr/index/filter.rb +139 -0
  97. data/lib/ddr/index/query.rb +82 -0
  98. data/lib/ddr/index/query_builder.rb +185 -0
  99. data/lib/ddr/index/query_clause.rb +112 -0
  100. data/lib/ddr/index/query_params.rb +40 -0
  101. data/lib/ddr/index/query_result.rb +102 -0
  102. data/lib/ddr/index/response.rb +30 -0
  103. data/lib/ddr/index/sort_order.rb +28 -0
  104. data/lib/ddr/index/unique_key_field.rb +12 -0
  105. data/lib/ddr/managers.rb +9 -0
  106. data/lib/ddr/managers/manager.rb +13 -0
  107. data/lib/ddr/managers/technical_metadata_manager.rb +141 -0
  108. data/lib/ddr/structure.rb +188 -0
  109. data/lib/ddr/structures/agent.rb +49 -0
  110. data/lib/ddr/structures/component_type_term.rb +29 -0
  111. data/lib/ddr/structures/div.rb +64 -0
  112. data/lib/ddr/structures/f_locat.rb +54 -0
  113. data/lib/ddr/structures/file.rb +52 -0
  114. data/lib/ddr/structures/file_grp.rb +35 -0
  115. data/lib/ddr/structures/file_sec.rb +22 -0
  116. data/lib/ddr/structures/fptr.rb +31 -0
  117. data/lib/ddr/structures/mets_hdr.rb +37 -0
  118. data/lib/ddr/structures/mptr.rb +49 -0
  119. data/lib/ddr/structures/struct_map.rb +40 -0
  120. data/lib/ddr/utils.rb +185 -0
  121. data/lib/ddr/vocab.rb +22 -0
  122. data/lib/ddr/vocab/asset.rb +51 -0
  123. data/lib/ddr/vocab/contact.rb +9 -0
  124. data/lib/ddr/vocab/display.rb +9 -0
  125. data/lib/ddr/vocab/duke_terms.rb +13 -0
  126. data/lib/ddr/vocab/rdf_vocabulary_parser.rb +43 -0
  127. data/lib/ddr/vocab/roles.rb +25 -0
  128. data/lib/ddr/vocab/sources/duketerms.rdf +870 -0
  129. data/lib/ddr/vocab/vocabulary.rb +37 -0
  130. data/lib/ddr/workflow.rb +8 -0
  131. data/lib/tasks/ddr/core_tasks.rake +4 -0
  132. metadata +428 -0
@@ -0,0 +1,74 @@
1
+ require "net-ldap"
2
+
3
+ module Ddr::Auth
4
+ class LdapGateway
5
+
6
+ SCOPE = Net::LDAP::SearchScope_SingleLevel
7
+
8
+ class_attribute :attributes
9
+ self.attributes = [ "edupersonaffiliation", "ismemberof" ]
10
+
11
+ attr_reader :ldap
12
+
13
+ def self.find(user_key)
14
+ new.find(user_key)
15
+ end
16
+
17
+ def initialize
18
+ @ldap = Net::LDAP.new(config)
19
+ end
20
+
21
+ def find(user_key)
22
+ result_set = ldap.search find_params(user_key)
23
+ if result_set
24
+ Result.new result_set.first
25
+ else
26
+ raise ldap.get_operation_result.message
27
+ end
28
+ end
29
+
30
+ class Result
31
+ attr_reader :result
32
+
33
+ def initialize(result)
34
+ @result = result
35
+ end
36
+
37
+ def affiliation
38
+ result ? result[:edupersonaffiliation] : []
39
+ end
40
+
41
+ def ismemberof
42
+ result ? result[:ismemberof] : []
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def find_params(user_key)
49
+ { scope: SCOPE,
50
+ filter: filter(user_key),
51
+ size: 1,
52
+ attributes: attributes
53
+ }
54
+ end
55
+
56
+ def filter(user_key)
57
+ Net::LDAP::Filter.eq("eduPersonPrincipalName", user_key)
58
+ end
59
+
60
+ def config
61
+ { host: ENV["LDAP_HOST"],
62
+ port: ENV["LDAP_PORT"],
63
+ base: ENV["LDAP_BASE"],
64
+ auth:
65
+ { method: :simple,
66
+ username: ENV["LDAP_USER"],
67
+ password: ENV["LDAP_PASSWORD"]
68
+ },
69
+ encryption: { method: :simple_tls }
70
+ }
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,18 @@
1
+ module Ddr::Auth
2
+ class Permissions
3
+
4
+ READ = :read
5
+ DOWNLOAD = :download
6
+ ADD_CHILDREN = :add_children
7
+ UPDATE = :update
8
+ REPLACE = :replace
9
+ ARRANGE = :arrange
10
+ PUBLISH = :publish
11
+ UNPUBLISH = :unpublish
12
+ AUDIT = :audit
13
+ GRANT = :grant
14
+
15
+ ALL = [ READ, DOWNLOAD, ADD_CHILDREN, UPDATE, REPLACE, ARRANGE, PUBLISH, UNPUBLISH, AUDIT, GRANT ]
16
+
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ module Ddr::Auth
2
+ class RemoteGroups
3
+
4
+ # @param auth_context [AuthContext]
5
+ # @return [Array<Group>]
6
+ def self.call(auth_context)
7
+ auth_context.ismemberof.map do |id|
8
+ Group.new id.sub(/\Aurn:mace:duke\.edu:groups/, "duke")
9
+ end
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,56 @@
1
+ module Ddr
2
+ module Auth
3
+ #
4
+ # Hydra controller mixin for role-based access control
5
+ #
6
+ # Overrides Hydra::AccessControlsEnforcement#gated_discovery_filters
7
+ # to apply role filters instead of permissions filters.
8
+ #
9
+ module RoleBasedAccessControlsEnforcement
10
+
11
+ def self.included(controller)
12
+ controller.delegate :authorized_to_act_as_superuser?, to: :current_ability
13
+ controller.helper_method :authorized_to_act_as_superuser?
14
+ end
15
+
16
+ def current_ability
17
+ @current_ability ||= AbilityFactory.call(current_user, request.env)
18
+ end
19
+
20
+ # List of URIs for policies on which any of the current user's agent has a role in policy scope
21
+ def policy_role_policies
22
+ @policy_role_policies ||= Array.new.tap do |uris|
23
+ filters = current_ability.agents.map do |agent|
24
+ "#{Ddr::Index::Fields::POLICY_ROLE}:\"#{agent}\""
25
+ end.join(" OR ")
26
+ query = "#{Ddr::Index::Fields::ACTIVE_FEDORA_MODEL}:Collection AND (#{filters})"
27
+ results = ActiveFedora::SolrService.query(query, rows: Collection.count, fl: Ddr::Index::Fields::INTERNAL_URI)
28
+ results.each_with_object(uris) { |r, memo| memo << r[Ddr::Index::Fields::INTERNAL_URI] }
29
+ end
30
+ end
31
+
32
+ def policy_role_filters
33
+ if policy_role_policies.present?
34
+ rels = policy_role_policies.map { |pid| [:is_governed_by, pid] }
35
+ ActiveFedora::SolrService.construct_query_for_rel(rels, "OR")
36
+ end
37
+ end
38
+
39
+ def resource_role_filters
40
+ current_ability.agents.map do |agent|
41
+ ActiveFedora::SolrService.raw_query(Ddr::Index::Fields::RESOURCE_ROLE, agent)
42
+ end.join(" OR ")
43
+ end
44
+
45
+ def gated_discovery_filters
46
+ [resource_role_filters, policy_role_filters].compact
47
+ end
48
+
49
+ # Overrides Hydra::AccessControlsEnforcement
50
+ def enforce_show_permissions
51
+ authorize! :read, params[:id]
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,28 @@
1
+ module Ddr::Auth
2
+ module Roles
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Role
6
+ autoload :RoleType
7
+ autoload :RoleTypes
8
+
9
+ include RoleTypes
10
+
11
+ RESOURCE_SCOPE = "resource".freeze
12
+ POLICY_SCOPE = "policy".freeze
13
+ SCOPES = [RESOURCE_SCOPE, POLICY_SCOPE].freeze
14
+
15
+ class << self
16
+
17
+ def type_map
18
+ @type_map ||= role_types.map { |role_type| [role_type.to_s, role_type] }.to_h
19
+ end
20
+
21
+ def role_types
22
+ @role_types ||= RoleTypes.constants(false).map { |const| RoleTypes.const_get(const) }
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,121 @@
1
+ module Ddr
2
+ module Auth
3
+ module Roles
4
+ #
5
+ # The assignment of a role to an agent within a scope.
6
+ #
7
+ class Role < Valkyrie::Resource
8
+
9
+ DEFAULT_SCOPE = Roles::RESOURCE_SCOPE
10
+
11
+ ValidScope = Valkyrie::Types::Strict::String.enum(*(Roles::SCOPES))
12
+ ValidRoleType = Valkyrie::Types::Strict::String.enum(*(Roles::role_types.map(&:title)))
13
+
14
+ attribute :agent, Valkyrie::Types::Strict::String.constrained(min_size: 1)
15
+ attribute :role_type, ValidRoleType
16
+ attribute :scope, ValidScope
17
+
18
+ class << self
19
+
20
+ # Build a Role instance from hash attributes
21
+ # @param args [Hash] the attributes
22
+ # @return [Role] the role
23
+ # @example
24
+ # Role.build type: "Curator", agent: "bob", scope: "resource"
25
+ def build(args={})
26
+ new.tap do |role|
27
+ args[:role_type] ||= args.delete(:type)
28
+ args[:agent] ||= nil # Triggers a constraint error
29
+ args[:agent] = args[:agent].to_s # Coerce Ddr::Auth:Group to string
30
+
31
+ args.each do |attr, val|
32
+ role.set_value(attr, val)
33
+ end
34
+
35
+ role.scope ||= DEFAULT_SCOPE
36
+ end
37
+ end
38
+
39
+ ###############
40
+ # FIXME or remove serialization/deserialization
41
+ ###############
42
+ #
43
+ # # Deserialize a Role from JSON
44
+ # # @param json [String] the JSON string
45
+ # # @return [Role] the role
46
+ # def from_json(json)
47
+ # build JSON.parse(json)
48
+ # end
49
+
50
+ # alias_method :deserialize, :from_json
51
+
52
+ private
53
+
54
+ #
55
+ # DELETEME
56
+ #
57
+ # def build_attributes(args={})
58
+ # # symbolize keys and stringify values
59
+ # attrs = args.each_with_object({}) do |(k, v), memo|
60
+ # memo[k.to_sym] = Array(v).first.to_s
61
+ # end
62
+ # # set default scope if necessary
63
+ # attrs[:scope] ||= DEFAULT_SCOPE
64
+ # # accept :type key for role_type attribute
65
+ # if attrs.key?(:type)
66
+ # attrs[:role_type] = attrs.delete(:type)
67
+ # end
68
+ # attrs
69
+ # end
70
+
71
+ end # class << self
72
+
73
+ # Roles are considered equal (==) if they
74
+ # are of the same type and have the same agent and scope.
75
+ # @param other [Object] the object of comparison
76
+ # @return [Boolean] the result
77
+ def ==(other)
78
+ self.class == other.class &&
79
+ role_type == other.role_type &&
80
+ scope == other.scope &&
81
+ agent == other.agent
82
+ end
83
+
84
+ alias_method :eql?, :==
85
+
86
+ def in_resource_scope?
87
+ scope == Roles::RESOURCE_SCOPE
88
+ end
89
+
90
+ def in_policy_scope?
91
+ scope == Roles::POLICY_SCOPE
92
+ end
93
+
94
+ def inspect
95
+ "#<#{self.class.name} role_type=#{role_type.inspect}, " \
96
+ "agent=#{agent.inspect}, scope=#{scope.inspect}>"
97
+ end
98
+
99
+ # TODO refactor up?
100
+ def proper_attributes
101
+ attributes.slice(self.class.fields - self.class.reserved_attributes)
102
+ end
103
+
104
+ ###############
105
+ # FIXME or remove serialization/deserialization
106
+ ###############
107
+ #
108
+ # delegate :to_json, to: :proper_attributes
109
+ #
110
+ # alias_method :serialize, :to_json
111
+
112
+ # Returns the permissions associated with the role
113
+ # @return [Array<Symbol>] the permissions
114
+ def permissions
115
+ Roles.type_map[role_type].permissions
116
+ end
117
+
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,23 @@
1
+ module Ddr
2
+ module Auth
3
+ module Roles
4
+ class RoleType
5
+
6
+ attr_reader :title, :description, :permissions
7
+ alias_method :label, :title
8
+
9
+ def initialize(title, description, permissions)
10
+ @title = title.freeze
11
+ @description = description.freeze
12
+ @permissions = permissions.freeze
13
+ freeze
14
+ end
15
+
16
+ def to_s
17
+ title
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,52 @@
1
+ module Ddr
2
+ module Auth
3
+ module Roles
4
+ module RoleTypes
5
+
6
+ CURATOR = RoleType.new(
7
+ "Curator",
8
+ "The Curator role conveys responsibility for curating a resource " \
9
+ "and delegating responsibilities to other agents.",
10
+ Permissions::ALL
11
+ )
12
+
13
+ EDITOR = RoleType.new(
14
+ "Editor",
15
+ "The Editor role conveys reponsibility for managing the content, " \
16
+ "description and structural arrangement of a resource.",
17
+ [ Permissions::READ, Permissions::DOWNLOAD, Permissions::ADD_CHILDREN,
18
+ Permissions::UPDATE, Permissions::REPLACE, Permissions::ARRANGE ]
19
+ )
20
+
21
+ METADATA_EDITOR = RoleType.new(
22
+ "MetadataEditor",
23
+ "The Metadata Editor role conveys responsibility for " \
24
+ "managing the description of a resource.",
25
+ [ Permissions::READ, Permissions::DOWNLOAD, Permissions::UPDATE ]
26
+ )
27
+
28
+ CONTRIBUTOR = RoleType.new(
29
+ "Contributor",
30
+ "The Contributor role conveys responsibility for adding related " \
31
+ "resources to a resource, such as works to a collection.",
32
+ [ Permissions::READ, Permissions::ADD_CHILDREN ]
33
+ )
34
+
35
+ DOWNLOADER = RoleType.new(
36
+ "Downloader",
37
+ "The Downloader role conveys access to the \"master\" file " \
38
+ "(original content bitstream) of a resource.",
39
+ [ Permissions::READ, Permissions::DOWNLOAD ]
40
+ )
41
+
42
+ VIEWER = RoleType.new(
43
+ "Viewer",
44
+ "The Viewer role conveys access to the description and \"access\" " \
45
+ "files (e.g., derivative bitstreams) of a resource.",
46
+ [ Permissions::READ ]
47
+ )
48
+
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,7 @@
1
+ module Ddr::Auth
2
+ class SuperuserAbility < AbstractAbility
3
+
4
+ self.ability_definitions = [ SuperuserAbilityDefinitions ]
5
+
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ module Ddr
2
+ module Auth
3
+ module TestHelpers
4
+
5
+ class MockLdapGateway
6
+ def self.find(*args); new.find(*args); end
7
+ def find(user_key); Ddr::Auth::LdapGateway::Result.new(nil); end
8
+ end
9
+
10
+ class MockGrouperGateway
11
+ def self.repository_groups(*args); new.repository_groups(*args); end
12
+ def repository_groups(raw = false); []; end
13
+ def self.user_groups(*args); new.user_groups(*args); end
14
+ def user_groups(user, raw = false); []; end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+
21
+ Ddr::Auth.ldap_gateway = Ddr::Auth::TestHelpers::MockLdapGateway
22
+ Ddr::Auth.grouper_gateway = Ddr::Auth::TestHelpers::MockGrouperGateway
@@ -0,0 +1,54 @@
1
+ require 'devise'
2
+
3
+ module Ddr::Auth
4
+ module User
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ delegate :can, :can?, :cannot, :cannot?, to: :ability
9
+
10
+ validates_uniqueness_of :username, case_sensitive: false
11
+ validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/
12
+
13
+ devise :database_authenticatable, :omniauthable, omniauth_providers: [:shibboleth]
14
+
15
+ class_attribute :user_key_attribute
16
+ self.user_key_attribute = Devise.authentication_keys.first
17
+ end
18
+
19
+ module ClassMethods
20
+ def find_by_user_key(key)
21
+ send("find_by_#{user_key_attribute}", key)
22
+ end
23
+
24
+ def from_omniauth(auth)
25
+ user = find_by_user_key(auth.uid) ||
26
+ new(user_key_attribute => auth.uid, :password => Devise.friendly_token)
27
+ user.update!(email: auth.info.email,
28
+ display_name: auth.info.name,
29
+ first_name: auth.info.first_name,
30
+ last_name: auth.info.last_name,
31
+ nickname: auth.info.nickname)
32
+ user
33
+ end
34
+ end
35
+
36
+ # Copied from Hydra::User
37
+ def user_key
38
+ send(user_key_attribute)
39
+ end
40
+
41
+ def to_s
42
+ user_key
43
+ end
44
+
45
+ def agent
46
+ user_key
47
+ end
48
+
49
+ def ability
50
+ @ability ||= AbilityFactory.call(self)
51
+ end
52
+
53
+ end
54
+ end