parse-stack-next 4.5.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.
- checksums.yaml +7 -0
- data/.bundle/config +2 -0
- data/.env.sample +112 -0
- data/.env.test +10 -0
- data/.github/workflows/ruby.yml +36 -0
- data/.gitignore +49 -0
- data/.ruby-version +1 -0
- data/.solargraph.yml +22 -0
- data/CHANGELOG.md +5816 -0
- data/Gemfile +30 -0
- data/Gemfile.lock +175 -0
- data/LICENSE.txt +23 -0
- data/Makefile +63 -0
- data/README.md +5655 -0
- data/Rakefile +573 -0
- data/bin/console +38 -0
- data/bin/parse-console +136 -0
- data/bin/server +17 -0
- data/bin/setup +7 -0
- data/config/parse-config.json +12 -0
- data/docs/TEST_SERVER.md +271 -0
- data/docs/_config.yml +1 -0
- data/docs/mcp_guide.md +3484 -0
- data/docs/mongodb_direct_guide.md +1348 -0
- data/docs/mongodb_index_optimization_guide.md +631 -0
- data/examples/transaction_example.rb +219 -0
- data/lib/parse/acl_scope.rb +728 -0
- data/lib/parse/agent/cancellation_token.rb +80 -0
- data/lib/parse/agent/constraint_translator.rb +480 -0
- data/lib/parse/agent/describe.rb +420 -0
- data/lib/parse/agent/errors.rb +133 -0
- data/lib/parse/agent/mcp_client.rb +557 -0
- data/lib/parse/agent/mcp_dispatcher.rb +1023 -0
- data/lib/parse/agent/mcp_rack_app.rb +1143 -0
- data/lib/parse/agent/mcp_server.rb +376 -0
- data/lib/parse/agent/metadata_audit.rb +259 -0
- data/lib/parse/agent/metadata_dsl.rb +733 -0
- data/lib/parse/agent/metadata_registry.rb +794 -0
- data/lib/parse/agent/pipeline_validator.rb +82 -0
- data/lib/parse/agent/prompts.rb +351 -0
- data/lib/parse/agent/rate_limiter.rb +158 -0
- data/lib/parse/agent/relation_graph.rb +162 -0
- data/lib/parse/agent/result_formatter.rb +453 -0
- data/lib/parse/agent/tools.rb +5489 -0
- data/lib/parse/agent.rb +3249 -0
- data/lib/parse/api/aggregate.rb +79 -0
- data/lib/parse/api/all.rb +26 -0
- data/lib/parse/api/analytics.rb +18 -0
- data/lib/parse/api/batch.rb +33 -0
- data/lib/parse/api/cloud_functions.rb +58 -0
- data/lib/parse/api/config.rb +125 -0
- data/lib/parse/api/files.rb +29 -0
- data/lib/parse/api/hooks.rb +117 -0
- data/lib/parse/api/objects.rb +146 -0
- data/lib/parse/api/path_segment.rb +75 -0
- data/lib/parse/api/push.rb +20 -0
- data/lib/parse/api/schema.rb +49 -0
- data/lib/parse/api/server.rb +50 -0
- data/lib/parse/api/sessions.rb +24 -0
- data/lib/parse/api/users.rb +250 -0
- data/lib/parse/atlas_search/index_manager.rb +353 -0
- data/lib/parse/atlas_search/result.rb +204 -0
- data/lib/parse/atlas_search/search_builder.rb +604 -0
- data/lib/parse/atlas_search/session.rb +253 -0
- data/lib/parse/atlas_search.rb +995 -0
- data/lib/parse/client/authentication.rb +97 -0
- data/lib/parse/client/batch.rb +234 -0
- data/lib/parse/client/body_builder.rb +240 -0
- data/lib/parse/client/caching.rb +203 -0
- data/lib/parse/client/logging.rb +293 -0
- data/lib/parse/client/profiling.rb +181 -0
- data/lib/parse/client/protocol.rb +91 -0
- data/lib/parse/client/request.rb +233 -0
- data/lib/parse/client/response.rb +208 -0
- data/lib/parse/client.rb +1104 -0
- data/lib/parse/clp_scope.rb +361 -0
- data/lib/parse/live_query/circuit_breaker.rb +256 -0
- data/lib/parse/live_query/client.rb +1001 -0
- data/lib/parse/live_query/configuration.rb +224 -0
- data/lib/parse/live_query/event.rb +115 -0
- data/lib/parse/live_query/event_queue.rb +272 -0
- data/lib/parse/live_query/health_monitor.rb +214 -0
- data/lib/parse/live_query/logging.rb +149 -0
- data/lib/parse/live_query/subscription.rb +294 -0
- data/lib/parse/live_query.rb +163 -0
- data/lib/parse/lookup_rewriter.rb +445 -0
- data/lib/parse/model/acl.rb +968 -0
- data/lib/parse/model/associations/belongs_to.rb +275 -0
- data/lib/parse/model/associations/collection_proxy.rb +435 -0
- data/lib/parse/model/associations/has_many.rb +597 -0
- data/lib/parse/model/associations/has_one.rb +158 -0
- data/lib/parse/model/associations/pointer_collection_proxy.rb +134 -0
- data/lib/parse/model/associations/relation_collection_proxy.rb +177 -0
- data/lib/parse/model/bytes.rb +62 -0
- data/lib/parse/model/classes/audience.rb +262 -0
- data/lib/parse/model/classes/installation.rb +363 -0
- data/lib/parse/model/classes/job_schedule.rb +153 -0
- data/lib/parse/model/classes/job_status.rb +264 -0
- data/lib/parse/model/classes/product.rb +75 -0
- data/lib/parse/model/classes/push_status.rb +263 -0
- data/lib/parse/model/classes/role.rb +751 -0
- data/lib/parse/model/classes/session.rb +201 -0
- data/lib/parse/model/classes/user.rb +943 -0
- data/lib/parse/model/clp.rb +544 -0
- data/lib/parse/model/core/actions.rb +1268 -0
- data/lib/parse/model/core/builder.rb +139 -0
- data/lib/parse/model/core/create_lock.rb +386 -0
- data/lib/parse/model/core/describe.rb +382 -0
- data/lib/parse/model/core/enhanced_change_tracking.rb +159 -0
- data/lib/parse/model/core/errors.rb +38 -0
- data/lib/parse/model/core/fetching.rb +566 -0
- data/lib/parse/model/core/field_guards.rb +220 -0
- data/lib/parse/model/core/indexing.rb +382 -0
- data/lib/parse/model/core/parse_reference.rb +407 -0
- data/lib/parse/model/core/properties.rb +809 -0
- data/lib/parse/model/core/querying.rb +491 -0
- data/lib/parse/model/core/schema.rb +202 -0
- data/lib/parse/model/core/search_indexing.rb +174 -0
- data/lib/parse/model/date.rb +88 -0
- data/lib/parse/model/email.rb +213 -0
- data/lib/parse/model/file.rb +527 -0
- data/lib/parse/model/geojson.rb +271 -0
- data/lib/parse/model/geopoint.rb +261 -0
- data/lib/parse/model/model.rb +260 -0
- data/lib/parse/model/object.rb +2068 -0
- data/lib/parse/model/phone.rb +520 -0
- data/lib/parse/model/pointer.rb +443 -0
- data/lib/parse/model/polygon.rb +406 -0
- data/lib/parse/model/push.rb +975 -0
- data/lib/parse/model/shortnames.rb +8 -0
- data/lib/parse/model/time_zone.rb +141 -0
- data/lib/parse/model/validations/uniqueness_validator.rb +97 -0
- data/lib/parse/model/validations.rb +96 -0
- data/lib/parse/mongodb.rb +2300 -0
- data/lib/parse/pipeline_security.rb +554 -0
- data/lib/parse/query/constraint.rb +198 -0
- data/lib/parse/query/constraints.rb +3279 -0
- data/lib/parse/query/cursor.rb +434 -0
- data/lib/parse/query/n_plus_one_detector.rb +445 -0
- data/lib/parse/query/operation.rb +104 -0
- data/lib/parse/query/ordering.rb +66 -0
- data/lib/parse/query.rb +7028 -0
- data/lib/parse/schema/index_migrator.rb +291 -0
- data/lib/parse/schema/search_index_migrator.rb +289 -0
- data/lib/parse/schema.rb +494 -0
- data/lib/parse/stack/generators/rails.rb +40 -0
- data/lib/parse/stack/generators/templates/model.erb +51 -0
- data/lib/parse/stack/generators/templates/model_installation.rb +4 -0
- data/lib/parse/stack/generators/templates/model_role.rb +4 -0
- data/lib/parse/stack/generators/templates/model_session.rb +4 -0
- data/lib/parse/stack/generators/templates/model_user.rb +11 -0
- data/lib/parse/stack/generators/templates/parse.rb +12 -0
- data/lib/parse/stack/generators/templates/webhooks.rb +10 -0
- data/lib/parse/stack/railtie.rb +18 -0
- data/lib/parse/stack/tasks.rb +563 -0
- data/lib/parse/stack/version.rb +11 -0
- data/lib/parse/stack.rb +455 -0
- data/lib/parse/two_factor_auth/user_extension.rb +449 -0
- data/lib/parse/two_factor_auth.rb +310 -0
- data/lib/parse/webhooks/payload.rb +360 -0
- data/lib/parse/webhooks/registration.rb +199 -0
- data/lib/parse/webhooks/replay_protection.rb +189 -0
- data/lib/parse/webhooks.rb +510 -0
- data/lib/parse-stack-next.rb +5 -0
- data/lib/parse-stack.rb +5 -0
- data/parse-stack-next.gemspec +82 -0
- data/parse-stack.png +0 -0
- data/scripts/debug-ips.js +35 -0
- data/scripts/docker/Dockerfile.parse +13 -0
- data/scripts/docker/atlas-init.js +284 -0
- data/scripts/docker/docker-compose.atlas.yml +76 -0
- data/scripts/docker/docker-compose.test.yml +106 -0
- data/scripts/docker/mongo-init.js +21 -0
- data/scripts/eval_mcp_with_lm_studio.rb +274 -0
- data/scripts/start-parse.sh +90 -0
- data/scripts/start_mcp_server.rb +78 -0
- data/scripts/test_server_connection.rb +82 -0
- metadata +377 -0
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Parse
|
|
5
|
+
|
|
6
|
+
# This class allows you to define custom data types for your model fields. You
|
|
7
|
+
# can define a subclass that implements the {DataType.typecast} method to
|
|
8
|
+
# convert to and from a value for serialization. The {Parse::ACL} class is
|
|
9
|
+
# implemented in this fashion.
|
|
10
|
+
class DataType
|
|
11
|
+
include ::ActiveModel::Model
|
|
12
|
+
include ::ActiveModel::Serializers::JSON
|
|
13
|
+
|
|
14
|
+
# @return [Hash] the set of attributes for this data type.
|
|
15
|
+
def attributes
|
|
16
|
+
{}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Transform an incoming value to another. The default implementation does
|
|
20
|
+
# returns the original value. This method should return an instance of a
|
|
21
|
+
# DataType subclass.
|
|
22
|
+
# @param value [Object] the input value to be typecasted.
|
|
23
|
+
# @param opts [Hash] a set of options to be used when typecasting.
|
|
24
|
+
# @return [Object]
|
|
25
|
+
def self.typecast(value, **opts)
|
|
26
|
+
value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Serialize this DataType into an JSON object hash format to be saved to Parse.
|
|
30
|
+
# The default implementation returns an empty hash.
|
|
31
|
+
# @return [Hash]
|
|
32
|
+
def as_json(*args)
|
|
33
|
+
{}.as_json
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# An ACL represents the dirty-trackable Parse Permissions object used for
|
|
38
|
+
# each record. In Parse, it is composed a hash-like object that represent
|
|
39
|
+
# {Parse::User} objectIds and/or a set of {Parse::Role} names. For each entity
|
|
40
|
+
# (ex. User/Role/Public), you can define read/write privileges on a particular
|
|
41
|
+
# record through a {Parse::ACL::Permission} instance.
|
|
42
|
+
#
|
|
43
|
+
# If you want to give privileges for an action (ex. read/write),
|
|
44
|
+
# you set that particular permission it to true. If you want to deny a
|
|
45
|
+
# permission, then you set it to false. Any denied permissions will not be
|
|
46
|
+
# part of the final hash structure that is sent to parse, as omission of a permission
|
|
47
|
+
# means denial.
|
|
48
|
+
#
|
|
49
|
+
# An ACL is represented by a JSON object with the keys being Parse::User object
|
|
50
|
+
# ids or the special key of "*", which indicates the public access permissions.
|
|
51
|
+
# The value of each key in the hash is a {Parse::ACL::Permission} object which
|
|
52
|
+
# defines the boolean permission state for read and write.
|
|
53
|
+
# The example below illustrates a Parse ACL JSON object where there is a *public*
|
|
54
|
+
# read permission, but public write is prevented. In addition, the user with
|
|
55
|
+
# id "*3KmCvT7Zsb*" is allowed to both read and write this record, and the "*Admins*"
|
|
56
|
+
# role is also allowed write access.
|
|
57
|
+
#
|
|
58
|
+
# {
|
|
59
|
+
# "*": { "read": true },
|
|
60
|
+
# "3KmCvT7Zsb": { "read": true, "write": true },
|
|
61
|
+
# "role:Admins": { "write": true }
|
|
62
|
+
# }
|
|
63
|
+
#
|
|
64
|
+
# All Parse::Object subclasses have an acl property by default. With this
|
|
65
|
+
# property, you can apply and delete permissions for this particular Parse
|
|
66
|
+
# object record.
|
|
67
|
+
# user = Parse::User.first
|
|
68
|
+
# artist = Artist.first
|
|
69
|
+
#
|
|
70
|
+
# artist.acl # "*": { "read": true, "write": true }
|
|
71
|
+
#
|
|
72
|
+
# # apply public read, but no public write
|
|
73
|
+
# artist.acl.everyone true, false
|
|
74
|
+
#
|
|
75
|
+
#
|
|
76
|
+
# # allow user to have read and write access
|
|
77
|
+
# artist.acl.apply user.id, true, true
|
|
78
|
+
#
|
|
79
|
+
# # remove all permissions for this user id
|
|
80
|
+
# artist.acl.delete user.id
|
|
81
|
+
#
|
|
82
|
+
# # allow the 'Admins' role read and write
|
|
83
|
+
# artist.acl.apply_role "Admins", true, true
|
|
84
|
+
#
|
|
85
|
+
# # remove write from all attached privileges
|
|
86
|
+
# artist.acl.no_write!
|
|
87
|
+
#
|
|
88
|
+
# # remove all attached privileges
|
|
89
|
+
# artist.acl.master_key_only!
|
|
90
|
+
#
|
|
91
|
+
# artist.save
|
|
92
|
+
#
|
|
93
|
+
# You may also set default ACLs for your subclasses by using {Parse::Object.set_default_acl}.
|
|
94
|
+
# These will be get applied for newly created instances. All subclasses have
|
|
95
|
+
# public read and write enabled by default.
|
|
96
|
+
#
|
|
97
|
+
# class AdminData < Parse::Object
|
|
98
|
+
#
|
|
99
|
+
# # Disable public read and write
|
|
100
|
+
# set_default_acl :public, read: true, write: false
|
|
101
|
+
#
|
|
102
|
+
# # Allow Admin roles to read/write
|
|
103
|
+
# set_default_acl 'Admin', role: true, read: true, write: true
|
|
104
|
+
#
|
|
105
|
+
# end
|
|
106
|
+
#
|
|
107
|
+
# data = AdminData.new
|
|
108
|
+
# data.acl # => ACL({"role:Admin"=>{"read"=>true, "write"=>true}})
|
|
109
|
+
#
|
|
110
|
+
# For more information about Parse record ACLs, see the documentation on
|
|
111
|
+
# {http://docs.parseplatform.org/rest/guide/#security Security}.
|
|
112
|
+
class ACL < DataType
|
|
113
|
+
|
|
114
|
+
# @!attribute delegate
|
|
115
|
+
# The instance object to be notified of changes. The delegate must support
|
|
116
|
+
# receiving a {Parse::Object#acl_will_change!} method.
|
|
117
|
+
# @return [Parse::Object]
|
|
118
|
+
attr_writer :permissions
|
|
119
|
+
attr_accessor :delegate
|
|
120
|
+
|
|
121
|
+
# @!attribute [rw] permissions
|
|
122
|
+
# Contains a hash structure of permissions, with keys mapping to either Public '*',
|
|
123
|
+
# a role name or an objectId for a user and values of type {ACL::Permission}. If you
|
|
124
|
+
# modify this attribute directly, you should call {Parse::Object#acl_will_change!}
|
|
125
|
+
# on the target object in order for dirty tracking to register changes.
|
|
126
|
+
# @example
|
|
127
|
+
# object.acl.permissions
|
|
128
|
+
# # => { "*": { "read": true }, "3KmCvT7Zsb": { "read": true, "write": true } }
|
|
129
|
+
# @return [Hash] a hash of permissions.
|
|
130
|
+
def permissions
|
|
131
|
+
@permissions ||= {}
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# The key field value for public permissions.
|
|
135
|
+
PUBLIC = "*".freeze
|
|
136
|
+
|
|
137
|
+
# Create a new ACL with default Public read/write permissions and any
|
|
138
|
+
# overrides from the input hash format.
|
|
139
|
+
# @param acls [Hash] a Parse-compatible hash acl format.
|
|
140
|
+
# @param owner [Parse::Object] a delegate to receive notifications of acl changes.
|
|
141
|
+
# This delegate must support receiving `acl_will_change!` method.
|
|
142
|
+
def initialize(acls = nil, owner: nil)
|
|
143
|
+
acls = acls.as_json if acls.is_a?(ACL)
|
|
144
|
+
self.attributes = acls if acls.is_a?(Hash)
|
|
145
|
+
@delegate = owner
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Create a new ACL with default Public read/write permissions and any
|
|
149
|
+
# overrides from the input hash format.
|
|
150
|
+
# @param read [Boolean] the read permissions for PUBLIC (default: true)
|
|
151
|
+
# @param write [Boolean] the write permissions for PUBLIC (default: true)
|
|
152
|
+
# @version 1.7.0
|
|
153
|
+
def self.everyone(read = true, write = true)
|
|
154
|
+
acl = Parse::ACL.new
|
|
155
|
+
acl.everyone(read, write)
|
|
156
|
+
acl
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Create a new private ACL with no public access.
|
|
160
|
+
# Objects with this ACL can only be accessed with the master key.
|
|
161
|
+
# @return [Parse::ACL] an empty ACL with no permissions.
|
|
162
|
+
# @version 3.1.3
|
|
163
|
+
# @example
|
|
164
|
+
# acl = Parse::ACL.private
|
|
165
|
+
# acl.as_json # => {}
|
|
166
|
+
def self.private
|
|
167
|
+
Parse::ACL.new
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Create a new ACL::Permission instance with the supplied read and write values.
|
|
171
|
+
# @param read [Boolean] the read permission value
|
|
172
|
+
# @param write [Boolean] the write permission value.
|
|
173
|
+
# @return [ACL::Permission]
|
|
174
|
+
# @see ACL::Permission
|
|
175
|
+
def self.permission(read, write = nil)
|
|
176
|
+
ACL::Permission.new(read, write)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Build a MongoDB +$match+-shaped predicate that matches documents
|
|
180
|
+
# readable by any of +permissions+. The canonical shape Parse
|
|
181
|
+
# Server enforces at the REST/SDK layer:
|
|
182
|
+
#
|
|
183
|
+
# { "$or" => [
|
|
184
|
+
# { "_rperm" => { "$in" => permissions } },
|
|
185
|
+
# { "_rperm" => { "$exists" => false } }
|
|
186
|
+
# ]}
|
|
187
|
+
#
|
|
188
|
+
# The +$exists: false+ branch is mandatory. Parse Server treats a
|
|
189
|
+
# missing +_rperm+ field as publicly readable (the field is only
|
|
190
|
+
# written when an ACL exists), so dropping that branch silently
|
|
191
|
+
# hides every public document.
|
|
192
|
+
#
|
|
193
|
+
# Used by:
|
|
194
|
+
# * {Parse::Query::ACLReadableByConstraint} and
|
|
195
|
+
# {Parse::Query::ACLReadableByRoleConstraint} to compile
|
|
196
|
+
# +:ACL.readable_by+ / +:ACL.readable_by_role+ query
|
|
197
|
+
# constraints into aggregation pipelines.
|
|
198
|
+
# * {Parse::AtlasSearch} to enforce ACL on +$search+ pipelines
|
|
199
|
+
# that bypass Parse Server and query MongoDB directly.
|
|
200
|
+
#
|
|
201
|
+
# @param permissions [Array<String>] permission strings — user
|
|
202
|
+
# objectIds, +"role:RoleName"+, or +"*"+ for public.
|
|
203
|
+
# @param include_public [Boolean] when +true+ (default), +"*"+ is
|
|
204
|
+
# appended to +permissions+ if missing. Set +false+ for a
|
|
205
|
+
# strict-permissions check (e.g., +:ACL.readable_by => "none"+
|
|
206
|
+
# semantics).
|
|
207
|
+
# @return [Hash] a MongoDB +$or+ subexpression suitable for use
|
|
208
|
+
# inside a +$match+ stage.
|
|
209
|
+
# @example
|
|
210
|
+
# permissions = ["*", user.id] + user.acl_roles.to_a.map { |n| "role:#{n}" }
|
|
211
|
+
# pipeline << { "$match" => Parse::ACL.read_predicate(permissions) }
|
|
212
|
+
def self.read_predicate(permissions, include_public: true)
|
|
213
|
+
permission_predicate("_rperm", permissions, include_public: include_public)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Build a MongoDB +$match+-shaped predicate that matches documents
|
|
217
|
+
# writable by any of +permissions+. Mirrors {.read_predicate} on
|
|
218
|
+
# the +_wperm+ field. See {.read_predicate} for the shape and the
|
|
219
|
+
# significance of the +$exists: false+ branch.
|
|
220
|
+
# @param permissions [Array<String>] permission strings.
|
|
221
|
+
# @param include_public [Boolean] whether to append +"*"+.
|
|
222
|
+
# @return [Hash] a MongoDB +$or+ subexpression.
|
|
223
|
+
def self.write_predicate(permissions, include_public: true)
|
|
224
|
+
permission_predicate("_wperm", permissions, include_public: include_public)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# @!visibility private
|
|
228
|
+
# Shared implementation for {.read_predicate} and {.write_predicate}.
|
|
229
|
+
# Normalizes the permissions array (string-coerced, deduplicated,
|
|
230
|
+
# +"*"+ appended when +include_public+) and returns the +$or+
|
|
231
|
+
# subexpression.
|
|
232
|
+
def self.permission_predicate(field, permissions, include_public: true)
|
|
233
|
+
perms = Array(permissions).map(&:to_s).reject(&:empty?).uniq
|
|
234
|
+
perms << "*" if include_public && !perms.include?("*")
|
|
235
|
+
{
|
|
236
|
+
"$or" => [
|
|
237
|
+
{ field => { "$in" => perms } },
|
|
238
|
+
{ field => { "$exists" => false } },
|
|
239
|
+
],
|
|
240
|
+
}
|
|
241
|
+
end
|
|
242
|
+
# Determines whether two ACLs or a Parse-ACL hash is equivalent to this object.
|
|
243
|
+
# @example
|
|
244
|
+
# acl = Parse::ACL.new
|
|
245
|
+
# # create a public read-only ACL
|
|
246
|
+
# acl.apply :public, true, false
|
|
247
|
+
# acl.as_json # => {'*' => { "read" => true }}
|
|
248
|
+
#
|
|
249
|
+
# create a second instance with similar privileges
|
|
250
|
+
# acl2 = Parse::ACL.everyone(true, false)
|
|
251
|
+
# acl2.as_json # => {'*' => { "read" => true }}
|
|
252
|
+
#
|
|
253
|
+
# acl == acl2 # => true
|
|
254
|
+
# acl == {'*' => { "read" => true }} # hash ok too
|
|
255
|
+
#
|
|
256
|
+
# acl2.apply_role 'Admin', true, true # rw for Admins
|
|
257
|
+
# acl == acl2 # => false
|
|
258
|
+
#
|
|
259
|
+
# @return [Boolean] whether two ACLs have the same set of privileges.
|
|
260
|
+
def ==(other_acl)
|
|
261
|
+
return false unless other_acl.is_a?(self.class) || other_acl.is_a?(Hash)
|
|
262
|
+
as_json == other_acl.as_json
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Set the public read and write permissions.
|
|
266
|
+
# @param read [Boolean] the read permission state.
|
|
267
|
+
# @param write [Boolean] the write permission state.
|
|
268
|
+
# @return [Hash] the current public permissions.
|
|
269
|
+
def everyone(read, write)
|
|
270
|
+
apply(PUBLIC, read, write)
|
|
271
|
+
permissions[PUBLIC]
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
alias_method :world, :everyone
|
|
275
|
+
|
|
276
|
+
# Calls `acl_will_change!` on the delegate when the permissions have changed.
|
|
277
|
+
# All {Parse::Object} subclasses implement this method.
|
|
278
|
+
def will_change!
|
|
279
|
+
@delegate.acl_will_change! if @delegate.respond_to?(:acl_will_change!)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Removes a permission for an objectId or user.
|
|
283
|
+
# @overload delete(object)
|
|
284
|
+
# @param object [Parse::User] the user to revoke permissions.
|
|
285
|
+
# @overload delete(id)
|
|
286
|
+
# @param id [String] the objectId to revoke permissions.
|
|
287
|
+
def delete(id)
|
|
288
|
+
id = id.id if id.is_a?(Parse::Pointer)
|
|
289
|
+
if id.present? && permissions.has_key?(id)
|
|
290
|
+
will_change!
|
|
291
|
+
permissions.delete(id)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Apply a new permission with a given objectId, tag or :public.
|
|
296
|
+
# @overload apply(user, read = nil, write = nil)
|
|
297
|
+
# Set the read and write permissions for this user on this ACL.
|
|
298
|
+
# @param user [Parse::User] the user object.
|
|
299
|
+
# @param read [Boolean] the read permission.
|
|
300
|
+
# @param write [Boolean] the write permission.
|
|
301
|
+
# @overload apply(role, read = nil, write = nil)
|
|
302
|
+
# Set the read and write permissions for this role object on this ACL.
|
|
303
|
+
# @param role [Parse::Role] the role object.
|
|
304
|
+
# @param read [Boolean] the read permission.
|
|
305
|
+
# @param write [Boolean] the write permission.
|
|
306
|
+
# @overload apply(id, read = nil, write = nil)
|
|
307
|
+
# Set the read and write permissions for this objectId on this ACL.
|
|
308
|
+
# @param id [String|:public] the objectId for a {Parse::User}. If :public is passed,
|
|
309
|
+
# then the {Parse::ACL::PUBLIC} read and write permissions will be modified.
|
|
310
|
+
# @param read [Boolean] the read permission.
|
|
311
|
+
# @param write [Boolean] the write permission.
|
|
312
|
+
# @return [Hash] the current set of permissions.
|
|
313
|
+
# @see #apply_role
|
|
314
|
+
def apply(id, read = nil, write = nil)
|
|
315
|
+
return apply_role(id, read, write) if id.is_a?(Parse::Role)
|
|
316
|
+
id = id.id if id.is_a?(Parse::Pointer)
|
|
317
|
+
unless id.present?
|
|
318
|
+
raise ArgumentError, "Invalid argument applying ACLs: must be either objectId, role or :public"
|
|
319
|
+
end
|
|
320
|
+
id = PUBLIC if id.to_sym == :public
|
|
321
|
+
# create a new Permissions
|
|
322
|
+
permission = ACL.permission(read, write)
|
|
323
|
+
# if the input is already an Permission object, then set it directly
|
|
324
|
+
permission = read if read.is_a?(Parse::ACL::Permission)
|
|
325
|
+
if permission.is_a?(ACL::Permission)
|
|
326
|
+
if permissions[id.to_s] != permission
|
|
327
|
+
will_change! # dirty track
|
|
328
|
+
permissions[id.to_s] = permission
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
permissions
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
alias_method :add, :apply
|
|
336
|
+
|
|
337
|
+
# Apply a {Parse::Role} to this ACL.
|
|
338
|
+
# @overload apply_role(role, read = nil, write = nil)
|
|
339
|
+
# @param role [Parse::Role] the role object.
|
|
340
|
+
# @param read [Boolean] the read permission.
|
|
341
|
+
# @param write [Boolean] the write permission.
|
|
342
|
+
# @overload apply_role(role_name, read = nil, write = nil)
|
|
343
|
+
# @param role_name [String] the name of the role.
|
|
344
|
+
# @param read [Boolean] the read permission.
|
|
345
|
+
# @param write [Boolean] the write permission.
|
|
346
|
+
def apply_role(name, read = nil, write = nil)
|
|
347
|
+
name = name.name if name.is_a?(Parse::Role)
|
|
348
|
+
apply("role:#{name}", read, write)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
alias_method :add_role, :apply_role
|
|
352
|
+
|
|
353
|
+
# Used for object conversion when formatting the input/output value in
|
|
354
|
+
# Parse::Object properties
|
|
355
|
+
# @param value [Hash] a Parse ACL hash to construct a Parse::ACL instance.
|
|
356
|
+
# @param delegate [Object] any object that would need to be notified of changes.
|
|
357
|
+
# @return [ACL]
|
|
358
|
+
# @see Parse::DataType
|
|
359
|
+
def self.typecast(value, delegate = nil)
|
|
360
|
+
ACL.new(value, owner: delegate)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Used for JSON serialization. Only if an attribute is non-nil, do we allow it
|
|
364
|
+
# in the Permissions hash, since omission means denial of priviledge. If the
|
|
365
|
+
# permission value has neither read or write, then the entire record has been denied
|
|
366
|
+
# all privileges
|
|
367
|
+
# @return [Hash]
|
|
368
|
+
def attributes
|
|
369
|
+
permissions.select { |k, v| v.present? }.as_json
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# @!visibility private
|
|
373
|
+
def attributes=(h)
|
|
374
|
+
return unless h.is_a?(Hash)
|
|
375
|
+
will_change!
|
|
376
|
+
@permissions ||= {}
|
|
377
|
+
h.each do |k, v|
|
|
378
|
+
apply(k, v)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# @!visibility private
|
|
383
|
+
def inspect
|
|
384
|
+
"ACL(#{as_json.inspect})"
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# @return [Hash]
|
|
388
|
+
def as_json(*args)
|
|
389
|
+
permissions.select { |k, v| v.present? }.as_json
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# @return [Boolean] true if there are any permissions.
|
|
393
|
+
def present?
|
|
394
|
+
permissions.values.any? { |v| v.present? }
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# Removes all ACLs, which only allows requests using the Parse Server master key
|
|
398
|
+
# to query and modify the object.
|
|
399
|
+
# @example
|
|
400
|
+
# object.acl
|
|
401
|
+
# # { "*": { "read" : true },
|
|
402
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
|
403
|
+
# # "role:Admins": { "write": true }
|
|
404
|
+
# # }
|
|
405
|
+
# object.acl.master_key_only!
|
|
406
|
+
# # Outcome:
|
|
407
|
+
# # { }
|
|
408
|
+
# @version 1.7.2
|
|
409
|
+
# @return [Hash] The cleared permissions hash
|
|
410
|
+
def master_key_only!
|
|
411
|
+
will_change!
|
|
412
|
+
@permissions = {}
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
alias_method :clear!, :master_key_only!
|
|
416
|
+
|
|
417
|
+
# Grants read permission on all existing users and roles attached to this object.
|
|
418
|
+
# @example
|
|
419
|
+
# object.acl
|
|
420
|
+
# # { "*": { "read" : true },
|
|
421
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
|
422
|
+
# # "role:Admins": { "write": true }
|
|
423
|
+
# # }
|
|
424
|
+
# object.acl.all_read!
|
|
425
|
+
# # Outcome:
|
|
426
|
+
# # { "*": { "read" : true },
|
|
427
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
|
428
|
+
# # "role:Admins": { "read" : true, "write": true}
|
|
429
|
+
# # }
|
|
430
|
+
# @version 1.7.2
|
|
431
|
+
# @return [Array] list of ACL keys
|
|
432
|
+
def all_read!
|
|
433
|
+
will_change!
|
|
434
|
+
permissions.keys.each do |perm|
|
|
435
|
+
permissions[perm].read! true
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Grants write permission on all existing users and roles attached to this object.
|
|
440
|
+
# @example
|
|
441
|
+
# object.acl
|
|
442
|
+
# # { "*": { "read" : true },
|
|
443
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
|
444
|
+
# # "role:Admins": { "write": true }
|
|
445
|
+
# # }
|
|
446
|
+
# object.acl.all_write!
|
|
447
|
+
# # Outcome:
|
|
448
|
+
# # { "*": { "read" : true, "write": true },
|
|
449
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
|
450
|
+
# # "role:Admins": { "write": true }
|
|
451
|
+
# # }
|
|
452
|
+
# @version 1.7.2
|
|
453
|
+
# @return [Array] list of ACL keys
|
|
454
|
+
def all_write!
|
|
455
|
+
will_change!
|
|
456
|
+
permissions.keys.each do |perm|
|
|
457
|
+
permissions[perm].write! true
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# Denies read permission on all existing users and roles attached to this object.
|
|
462
|
+
# @example
|
|
463
|
+
# object.acl
|
|
464
|
+
# # { "*": { "read" : true },
|
|
465
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
|
466
|
+
# # "role:Admins": { "write": true }
|
|
467
|
+
# # }
|
|
468
|
+
# object.acl.no_read!
|
|
469
|
+
# # Outcome:
|
|
470
|
+
# # { "*": nil,
|
|
471
|
+
# # "3KmCvT7Zsb": { "write": true },
|
|
472
|
+
# # "role:Admins": { "write": true }
|
|
473
|
+
# # }
|
|
474
|
+
# @version 1.7.2
|
|
475
|
+
# @return [Array] list of ACL keys
|
|
476
|
+
def no_read!
|
|
477
|
+
will_change!
|
|
478
|
+
permissions.keys.each do |perm|
|
|
479
|
+
permissions[perm].read! false
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# Denies write permission on all existing users and roles attached to this object.
|
|
484
|
+
# @example
|
|
485
|
+
# object.acl
|
|
486
|
+
# # { "*": { "read" : true },
|
|
487
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
|
488
|
+
# # "role:Admins": { "write": true }
|
|
489
|
+
# # }
|
|
490
|
+
# object.acl.no_write!
|
|
491
|
+
# # Outcome:
|
|
492
|
+
# # { "*": { "read" : true },
|
|
493
|
+
# # "3KmCvT7Zsb": { "read" : true },
|
|
494
|
+
# # "role:Admins": nil
|
|
495
|
+
# # }
|
|
496
|
+
# @version 1.7.2
|
|
497
|
+
# @return [Array] list of ACL keys
|
|
498
|
+
def no_write!
|
|
499
|
+
will_change!
|
|
500
|
+
permissions.keys.each do |perm|
|
|
501
|
+
permissions[perm].write! false
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Returns an array of all user IDs and role names that have read access to this object.
|
|
506
|
+
# @return [Array<String>] list of user IDs and role names (e.g., ["*", "user123", "role:Admin"])
|
|
507
|
+
def readable_by
|
|
508
|
+
permissions.select { |k, v| v.read }.keys
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
# Returns an array of all user IDs and role names that have write access to this object.
|
|
512
|
+
# @return [Array<String>] list of user IDs and role names (e.g., ["*", "user123", "role:Admin"])
|
|
513
|
+
def writeable_by
|
|
514
|
+
permissions.select { |k, v| v.write }.keys
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
alias_method :writable_by, :writeable_by
|
|
518
|
+
|
|
519
|
+
# Checks if a specific user or role (or any in an array) has read access to this object.
|
|
520
|
+
# When passed an array of strings, returns true if ANY of the users/roles have read access (OR logic).
|
|
521
|
+
# When passed a Parse::User object or pointer, automatically fetches and checks the user's roles as well.
|
|
522
|
+
# @param user_or_role [String, Parse::User, Parse::Pointer, Array<String>] the user ID, role name, user object, user pointer, or array of user IDs/role names
|
|
523
|
+
# @return [Boolean] true if the user/role (or any in the array) has read access
|
|
524
|
+
# @example
|
|
525
|
+
# acl.readable_by?("user123") # Check single user ID
|
|
526
|
+
# acl.readable_by?("Admin") # Check single role name
|
|
527
|
+
# acl.readable_by?(user_object) # Check user + their roles
|
|
528
|
+
# acl.readable_by?(user_pointer) # Check user pointer + their roles
|
|
529
|
+
# acl.readable_by?(["user123", "Admin"]) # Check array (OR logic)
|
|
530
|
+
def readable_by?(user_or_role)
|
|
531
|
+
# Handle arrays - check if ANY item in the array has read access (OR logic)
|
|
532
|
+
if user_or_role.is_a?(Array)
|
|
533
|
+
# For arrays, just check each string value directly (no User object expansion)
|
|
534
|
+
return user_or_role.any? do |item|
|
|
535
|
+
key = normalize_permission_key(item)
|
|
536
|
+
key && permissions[key]&.read == true
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# Handle Parse::Pointer to User - expand to include user ID and roles
|
|
541
|
+
if user_or_role.is_a?(Parse::Pointer) || (user_or_role.respond_to?(:parse_class) && user_or_role.respond_to?(:id))
|
|
542
|
+
# Check if it's a pointer to a User
|
|
543
|
+
if user_or_role.respond_to?(:parse_class) && (user_or_role.parse_class == "User" || user_or_role.parse_class == "_User")
|
|
544
|
+
permissions_to_check = []
|
|
545
|
+
|
|
546
|
+
# Add the user ID from the pointer
|
|
547
|
+
user_id = user_or_role.respond_to?(:id) ? user_or_role.id : nil
|
|
548
|
+
permissions_to_check << user_id if user_id.present?
|
|
549
|
+
|
|
550
|
+
# Query roles directly using the user pointer (no need to fetch the full user)
|
|
551
|
+
begin
|
|
552
|
+
if user_id.present? && defined?(Parse::Role)
|
|
553
|
+
user_roles = Parse::Role.all(users: user_or_role)
|
|
554
|
+
user_roles.each do |role|
|
|
555
|
+
permissions_to_check << "role:#{role.name}" if role.respond_to?(:name) && role.name.present?
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
rescue
|
|
559
|
+
# If role fetching fails, continue with just the user ID
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
# Check if any of the user's permissions (user ID or roles) have read access
|
|
563
|
+
return readable_by?(permissions_to_check) if permissions_to_check.any?
|
|
564
|
+
return false
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# If it's a User object, expand it to include the user ID and all their roles
|
|
569
|
+
if user_or_role.is_a?(Parse::User) || (user_or_role.respond_to?(:is_a?) && user_or_role.is_a?(Parse::User))
|
|
570
|
+
permissions_to_check = []
|
|
571
|
+
|
|
572
|
+
# Add the user ID
|
|
573
|
+
permissions_to_check << user_or_role.id if user_or_role.respond_to?(:id) && user_or_role.id.present?
|
|
574
|
+
|
|
575
|
+
# Fetch and add all the user's roles
|
|
576
|
+
begin
|
|
577
|
+
if user_or_role.respond_to?(:id) && user_or_role.id.present? && defined?(Parse::Role)
|
|
578
|
+
user_roles = Parse::Role.all(users: user_or_role)
|
|
579
|
+
user_roles.each do |role|
|
|
580
|
+
permissions_to_check << "role:#{role.name}" if role.respond_to?(:name) && role.name.present?
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
rescue
|
|
584
|
+
# If role fetching fails, continue with just the user ID
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
# Check if any of the user's permissions (user ID or roles) have read access
|
|
588
|
+
# Use array checking logic (OR)
|
|
589
|
+
return readable_by?(permissions_to_check) if permissions_to_check.any?
|
|
590
|
+
return false
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# Single string value - check directly
|
|
594
|
+
key = normalize_permission_key(user_or_role)
|
|
595
|
+
return false unless key
|
|
596
|
+
permissions[key]&.read == true
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
# Checks if a specific user or role (or any in an array) has write access to this object.
|
|
600
|
+
# When passed an array of strings, returns true if ANY of the users/roles have write access (OR logic).
|
|
601
|
+
# When passed a Parse::User object or pointer, automatically fetches and checks the user's roles as well.
|
|
602
|
+
# @param user_or_role [String, Parse::User, Parse::Pointer, Array<String>] the user ID, role name, user object, user pointer, or array of user IDs/role names
|
|
603
|
+
# @return [Boolean] true if the user/role (or any in the array) has write access
|
|
604
|
+
# @example
|
|
605
|
+
# acl.writeable_by?("user123") # Check single user ID
|
|
606
|
+
# acl.writeable_by?("Admin") # Check single role name
|
|
607
|
+
# acl.writeable_by?(user_object) # Check user + their roles
|
|
608
|
+
# acl.writeable_by?(user_pointer) # Check user pointer + their roles
|
|
609
|
+
# acl.writeable_by?(["user123", "Admin"]) # Check array (OR logic)
|
|
610
|
+
def writeable_by?(user_or_role)
|
|
611
|
+
# Handle arrays - check if ANY item in the array has write access (OR logic)
|
|
612
|
+
if user_or_role.is_a?(Array)
|
|
613
|
+
# For arrays, just check each string value directly (no User object expansion)
|
|
614
|
+
return user_or_role.any? do |item|
|
|
615
|
+
key = normalize_permission_key(item)
|
|
616
|
+
key && permissions[key]&.write == true
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
# Handle Parse::Pointer to User - expand to include user ID and roles
|
|
621
|
+
if user_or_role.is_a?(Parse::Pointer) || (user_or_role.respond_to?(:parse_class) && user_or_role.respond_to?(:id))
|
|
622
|
+
# Check if it's a pointer to a User
|
|
623
|
+
if user_or_role.respond_to?(:parse_class) && (user_or_role.parse_class == "User" || user_or_role.parse_class == "_User")
|
|
624
|
+
permissions_to_check = []
|
|
625
|
+
|
|
626
|
+
# Add the user ID from the pointer
|
|
627
|
+
user_id = user_or_role.respond_to?(:id) ? user_or_role.id : nil
|
|
628
|
+
permissions_to_check << user_id if user_id.present?
|
|
629
|
+
|
|
630
|
+
# Query roles directly using the user pointer (no need to fetch the full user)
|
|
631
|
+
begin
|
|
632
|
+
if user_id.present? && defined?(Parse::Role)
|
|
633
|
+
user_roles = Parse::Role.all(users: user_or_role)
|
|
634
|
+
user_roles.each do |role|
|
|
635
|
+
permissions_to_check << "role:#{role.name}" if role.respond_to?(:name) && role.name.present?
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
rescue
|
|
639
|
+
# If role fetching fails, continue with just the user ID
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
# Check if any of the user's permissions (user ID or roles) have write access
|
|
643
|
+
return writeable_by?(permissions_to_check) if permissions_to_check.any?
|
|
644
|
+
return false
|
|
645
|
+
end
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
# If it's a User object, expand it to include the user ID and all their roles
|
|
649
|
+
if user_or_role.is_a?(Parse::User) || (user_or_role.respond_to?(:is_a?) && user_or_role.is_a?(Parse::User))
|
|
650
|
+
permissions_to_check = []
|
|
651
|
+
|
|
652
|
+
# Add the user ID
|
|
653
|
+
permissions_to_check << user_or_role.id if user_or_role.respond_to?(:id) && user_or_role.id.present?
|
|
654
|
+
|
|
655
|
+
# Fetch and add all the user's roles
|
|
656
|
+
begin
|
|
657
|
+
if user_or_role.respond_to?(:id) && user_or_role.id.present? && defined?(Parse::Role)
|
|
658
|
+
user_roles = Parse::Role.all(users: user_or_role)
|
|
659
|
+
user_roles.each do |role|
|
|
660
|
+
permissions_to_check << "role:#{role.name}" if role.respond_to?(:name) && role.name.present?
|
|
661
|
+
end
|
|
662
|
+
end
|
|
663
|
+
rescue
|
|
664
|
+
# If role fetching fails, continue with just the user ID
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
# Check if any of the user's permissions (user ID or roles) have write access
|
|
668
|
+
# Use array checking logic (OR)
|
|
669
|
+
return writeable_by?(permissions_to_check) if permissions_to_check.any?
|
|
670
|
+
return false
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# Single string value - check directly
|
|
674
|
+
key = normalize_permission_key(user_or_role)
|
|
675
|
+
return false unless key
|
|
676
|
+
permissions[key]&.write == true
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
alias_method :writable_by?, :writeable_by?
|
|
680
|
+
alias_method :can_read?, :readable_by?
|
|
681
|
+
alias_method :can_write?, :writeable_by?
|
|
682
|
+
|
|
683
|
+
# Checks if the object has public read access.
|
|
684
|
+
# @return [Boolean] true if public can read this object
|
|
685
|
+
def public_read?
|
|
686
|
+
permissions[PUBLIC]&.read == true
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
# Checks if the object has public write access.
|
|
690
|
+
# @return [Boolean] true if public can write to this object
|
|
691
|
+
def public_write?
|
|
692
|
+
permissions[PUBLIC]&.write == true
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
# Checks if the object has no read permissions for anyone (master key only).
|
|
696
|
+
# @return [Boolean] true if no one has read access
|
|
697
|
+
def no_read?
|
|
698
|
+
permissions.values.none? { |v| v.read }
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
# Checks if the object has no write permissions for anyone (master key only).
|
|
702
|
+
# @return [Boolean] true if no one has write access
|
|
703
|
+
def no_write?
|
|
704
|
+
permissions.values.none? { |v| v.write }
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
# Checks if the object is read-only (has read permissions but no write permissions).
|
|
708
|
+
# @return [Boolean] true if object has read access but no write access
|
|
709
|
+
def read_only?
|
|
710
|
+
permissions.values.any? { |v| v.read } && permissions.values.none? { |v| v.write }
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
# Checks if the object is write-only (has write permissions but no read permissions).
|
|
714
|
+
# @return [Boolean] true if object has write access but no read access
|
|
715
|
+
def write_only?
|
|
716
|
+
permissions.values.any? { |v| v.write } && permissions.values.none? { |v| v.read }
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
# Returns an array of all user IDs and role names that have both read and write access.
|
|
720
|
+
# @return [Array<String>] list of user IDs and role names with full access
|
|
721
|
+
def owners
|
|
722
|
+
permissions.select { |k, v| v.read && v.write }.keys
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
# Checks if a specific user or role (or any in an array) has both read and write access to this object.
|
|
726
|
+
# When passed an array of strings, returns true if ANY of the users/roles have both read and write access (OR logic).
|
|
727
|
+
# When passed a Parse::User object or pointer, automatically fetches and checks the user's roles as well.
|
|
728
|
+
# @param user_or_role [String, Parse::User, Parse::Pointer, Array<String>] the user ID, role name, user object, user pointer, or array of user IDs/role names
|
|
729
|
+
# @return [Boolean] true if the user/role (or any in the array) has both read and write access
|
|
730
|
+
# @example
|
|
731
|
+
# acl.owner?("user123") # Check single user ID
|
|
732
|
+
# acl.owner?("Admin") # Check single role name
|
|
733
|
+
# acl.owner?(user_object) # Check user + their roles
|
|
734
|
+
# acl.owner?(user_pointer) # Check user pointer + their roles
|
|
735
|
+
# acl.owner?(["user123", "Admin"]) # Check array (OR logic)
|
|
736
|
+
def owner?(user_or_role)
|
|
737
|
+
# Handle arrays - check if ANY item in the array is an owner (OR logic)
|
|
738
|
+
if user_or_role.is_a?(Array)
|
|
739
|
+
# For arrays, just check each string value directly (no User object expansion)
|
|
740
|
+
return user_or_role.any? do |item|
|
|
741
|
+
key = normalize_permission_key(item)
|
|
742
|
+
next false unless key
|
|
743
|
+
perm = permissions[key]
|
|
744
|
+
perm&.read == true && perm&.write == true
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
# Handle Parse::Pointer to User - expand to include user ID and roles
|
|
749
|
+
if user_or_role.is_a?(Parse::Pointer) || (user_or_role.respond_to?(:parse_class) && user_or_role.respond_to?(:id))
|
|
750
|
+
# Check if it's a pointer to a User
|
|
751
|
+
if user_or_role.respond_to?(:parse_class) && (user_or_role.parse_class == "User" || user_or_role.parse_class == "_User")
|
|
752
|
+
permissions_to_check = []
|
|
753
|
+
|
|
754
|
+
# Add the user ID from the pointer
|
|
755
|
+
user_id = user_or_role.respond_to?(:id) ? user_or_role.id : nil
|
|
756
|
+
permissions_to_check << user_id if user_id.present?
|
|
757
|
+
|
|
758
|
+
# Query roles directly using the user pointer (no need to fetch the full user)
|
|
759
|
+
begin
|
|
760
|
+
if user_id.present? && defined?(Parse::Role)
|
|
761
|
+
user_roles = Parse::Role.all(users: user_or_role)
|
|
762
|
+
user_roles.each do |role|
|
|
763
|
+
permissions_to_check << "role:#{role.name}" if role.respond_to?(:name) && role.name.present?
|
|
764
|
+
end
|
|
765
|
+
end
|
|
766
|
+
rescue
|
|
767
|
+
# If role fetching fails, continue with just the user ID
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
# Check if any of the user's permissions (user ID or roles) are owners
|
|
771
|
+
return owner?(permissions_to_check) if permissions_to_check.any?
|
|
772
|
+
return false
|
|
773
|
+
end
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
# If it's a User object, expand it to include the user ID and all their roles
|
|
777
|
+
if user_or_role.is_a?(Parse::User) || (user_or_role.respond_to?(:is_a?) && user_or_role.is_a?(Parse::User))
|
|
778
|
+
permissions_to_check = []
|
|
779
|
+
|
|
780
|
+
# Add the user ID
|
|
781
|
+
permissions_to_check << user_or_role.id if user_or_role.respond_to?(:id) && user_or_role.id.present?
|
|
782
|
+
|
|
783
|
+
# Fetch and add all the user's roles
|
|
784
|
+
begin
|
|
785
|
+
if user_or_role.respond_to?(:id) && user_or_role.id.present? && defined?(Parse::Role)
|
|
786
|
+
user_roles = Parse::Role.all(users: user_or_role)
|
|
787
|
+
user_roles.each do |role|
|
|
788
|
+
permissions_to_check << "role:#{role.name}" if role.respond_to?(:name) && role.name.present?
|
|
789
|
+
end
|
|
790
|
+
end
|
|
791
|
+
rescue
|
|
792
|
+
# If role fetching fails, continue with just the user ID
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
# Check if any of the user's permissions (user ID or roles) are owners
|
|
796
|
+
# Use array checking logic (OR)
|
|
797
|
+
return owner?(permissions_to_check) if permissions_to_check.any?
|
|
798
|
+
return false
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
# Single string value - check directly
|
|
802
|
+
key = normalize_permission_key(user_or_role)
|
|
803
|
+
return false unless key
|
|
804
|
+
perm = permissions[key]
|
|
805
|
+
perm&.read == true && perm&.write == true
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
# Checks if the ACL has no permissions (master key only access).
|
|
809
|
+
# @return [Boolean] true if no permissions exist
|
|
810
|
+
def empty?
|
|
811
|
+
permissions.empty? || permissions.values.none? { |v| v.present? }
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
alias_method :master_key_only?, :empty?
|
|
815
|
+
alias_method :master_only?, :empty?
|
|
816
|
+
|
|
817
|
+
private
|
|
818
|
+
|
|
819
|
+
# Normalizes a user or role input to the appropriate permission key format.
|
|
820
|
+
# @param user_or_role [String, Parse::User, Parse::Role] the input to normalize
|
|
821
|
+
# @return [String, nil] the normalized key or nil if invalid
|
|
822
|
+
def normalize_permission_key(user_or_role)
|
|
823
|
+
case user_or_role
|
|
824
|
+
when String
|
|
825
|
+
# Handle role names, user IDs, or public
|
|
826
|
+
if user_or_role == "*" || user_or_role.to_sym == :public
|
|
827
|
+
PUBLIC
|
|
828
|
+
elsif user_or_role.start_with?("role:")
|
|
829
|
+
user_or_role
|
|
830
|
+
else
|
|
831
|
+
# Could be a role name without prefix or a user ID
|
|
832
|
+
# Check if it exists as-is first, then try as role
|
|
833
|
+
if permissions.key?(user_or_role)
|
|
834
|
+
user_or_role
|
|
835
|
+
elsif permissions.key?("role:#{user_or_role}")
|
|
836
|
+
"role:#{user_or_role}"
|
|
837
|
+
else
|
|
838
|
+
user_or_role # Return as-is, might be a user ID
|
|
839
|
+
end
|
|
840
|
+
end
|
|
841
|
+
when Parse::User
|
|
842
|
+
user_or_role.id if user_or_role.respond_to?(:id)
|
|
843
|
+
when Parse::Role
|
|
844
|
+
"role:#{user_or_role.name}" if user_or_role.respond_to?(:name)
|
|
845
|
+
when Parse::Pointer
|
|
846
|
+
user_or_role.id if user_or_role.respond_to?(:id)
|
|
847
|
+
when Symbol
|
|
848
|
+
user_or_role == :public ? PUBLIC : user_or_role.to_s
|
|
849
|
+
else
|
|
850
|
+
nil
|
|
851
|
+
end
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
public
|
|
855
|
+
|
|
856
|
+
# The Permission class tracks the read and write permissions for a specific
|
|
857
|
+
# ACL entry. The value of an Parse-ACL hash only contains two keys: "read" and "write".
|
|
858
|
+
#
|
|
859
|
+
# # Example of the ACL format
|
|
860
|
+
# { "*": { "read": true },
|
|
861
|
+
# "3KmCvT7Zsb": { "read": true, "write": true },
|
|
862
|
+
# "role:Admins": { "write": true }
|
|
863
|
+
# }
|
|
864
|
+
# This would be managed as:
|
|
865
|
+
# { "*": ACL::Permission.new(true),
|
|
866
|
+
# "3KmCvT7Zsb": ACL::Permission.new(true, true)
|
|
867
|
+
# "role:Amdins": ACL::Permission.new(false,true)
|
|
868
|
+
# }
|
|
869
|
+
#
|
|
870
|
+
class Permission
|
|
871
|
+
include ::ActiveModel::Model
|
|
872
|
+
include ::ActiveModel::Serializers::JSON
|
|
873
|
+
|
|
874
|
+
# The *read* permission state.
|
|
875
|
+
# @return [Boolean] whether this permission is allowed.
|
|
876
|
+
attr_reader :read
|
|
877
|
+
|
|
878
|
+
# The *write* permission state.
|
|
879
|
+
# @return [Boolean] whether this permission is allowed.
|
|
880
|
+
attr_reader :write
|
|
881
|
+
|
|
882
|
+
# Create a new permission with the given read and write privileges.
|
|
883
|
+
# @overload new(read = nil, write = nil)
|
|
884
|
+
# @param read [Boolean] whether reading is allowed.
|
|
885
|
+
# @param write [Boolean] whether writing is allowed.
|
|
886
|
+
# @example
|
|
887
|
+
# ACL::Permission.new(true, false)
|
|
888
|
+
# @overload new(hash)
|
|
889
|
+
# @param hash [Hash] a key value pair for read/write permissions.
|
|
890
|
+
# @example
|
|
891
|
+
# ACL::Permission.new({read: true, write: false})
|
|
892
|
+
#
|
|
893
|
+
def initialize(r_perm = nil, w_perm = nil)
|
|
894
|
+
if r_perm.is_a?(Hash)
|
|
895
|
+
r_perm = r_perm.symbolize_keys
|
|
896
|
+
@read = r_perm[:read].present?
|
|
897
|
+
@write = r_perm[:write].present?
|
|
898
|
+
else
|
|
899
|
+
@read = r_perm.present?
|
|
900
|
+
@write = w_perm.present?
|
|
901
|
+
end
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
# @return [Boolean] whether two permission instances have the same permissions.
|
|
905
|
+
def ==(per)
|
|
906
|
+
return false unless per.is_a?(self.class)
|
|
907
|
+
@read == per.read && @write == per.write
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
# @return [Hash] A Parse-compatible ACL-hash. Omission or false on a
|
|
911
|
+
# priviledge means don't include it
|
|
912
|
+
def as_json(*args)
|
|
913
|
+
h = {}
|
|
914
|
+
h[:read] = true if @read
|
|
915
|
+
h[:write] = true if @write
|
|
916
|
+
h.empty? ? nil : h.as_json
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
# @return [Hash]
|
|
920
|
+
def attributes
|
|
921
|
+
h = {}
|
|
922
|
+
h.merge!(read: :boolean) if @read
|
|
923
|
+
h.merge!(write: :boolean) if @write
|
|
924
|
+
h
|
|
925
|
+
end
|
|
926
|
+
|
|
927
|
+
# @!visibility private
|
|
928
|
+
def inspect
|
|
929
|
+
as_json.inspect
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
# @return [Boolean] whether there is at least one permission set to true.
|
|
933
|
+
def present?
|
|
934
|
+
@read.present? || @write.present?
|
|
935
|
+
end
|
|
936
|
+
|
|
937
|
+
# Sets the *read* value of the permission. Defaults to true.
|
|
938
|
+
# @note Setting the value in this manner is not dirty tracked.
|
|
939
|
+
# @version 1.7.2
|
|
940
|
+
# @return [void]
|
|
941
|
+
def read!(value = true)
|
|
942
|
+
@read = value
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
# Sets the *write* value of the permission. Defaults to true.
|
|
946
|
+
# @note Setting the value in this manner is not dirty tracked.
|
|
947
|
+
# @version 1.7.2
|
|
948
|
+
# @return [void]
|
|
949
|
+
def write!(value = true)
|
|
950
|
+
@write = value
|
|
951
|
+
end
|
|
952
|
+
|
|
953
|
+
# Sets the *read* value of the permission to false.
|
|
954
|
+
# @version 1.7.2
|
|
955
|
+
# @return [void]
|
|
956
|
+
def no_read!
|
|
957
|
+
@read = false
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
# Sets the *write* value of the permission to false.
|
|
961
|
+
# @version 1.7.2
|
|
962
|
+
# @return [void]
|
|
963
|
+
def no_write!
|
|
964
|
+
@write = false
|
|
965
|
+
end
|
|
966
|
+
end
|
|
967
|
+
end
|
|
968
|
+
end
|