001 (ns dog-and-duck.quack.quack
002 "Validator for ActivityPub objects: if it walks like a duck, and it quacks
003 like a duck...
004
005 **NOTE THAT the ActivityPub spec
006 [says](https://www.w3.org/TR/activitypub/#obj)
007
008 > Servers SHOULD validate the content they receive to avoid content
009 > spoofing attacks
010
011 but in practice ActivityPub content collected in the wild bears only
012 a hazy relationship to the spec, so this is difficult. I suspect that
013 I may have to implement a `*strict*` dynamic variable, so that users can
014 toggle some checks off."
015
016 ;;(:require [clojure.spec.alpha as s])
017 (:require [dog-and-duck.quack.picky :refer [filter-severity has-context?
018 object-faults]])
019 (:import [java.net URI URISyntaxException]))
020
021 ;;; Copyright (C) Simon Brooke, 2022
022
023 ;;; This program is free software; you can redistribute it and/or
024 ;;; modify it under the terms of the GNU General Public License
025 ;;; as published by the Free Software Foundation; either version 2
026 ;;; of the License, or (at your option) any later version.
027
028 ;;; This program is distributed in the hope that it will be useful,
029 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
030 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
031 ;;; GNU General Public License for more details.
032
033 ;;; You should have received a copy of the GNU General Public License
034 ;;; along with this program; if not, write to the Free Software
035 ;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
036
037 (defn object?
038 "Returns `true` iff `x` is recognisably an ActivityStreams object.
039
040 **NOTE THAT** The ActivityStreams spec
041 [says](https://www.w3.org/TR/activitystreams-core/#object):
042
043 > All properties are optional (including the id and type)
044
045 But we are *just not having that*, because otherwise we're flying blind.
046 We *shall* reject objects lacking at least `:type`. Missing `:id` keys are
047 tolerable because they represent transient objects, which we expect to
048 handle.
049
050 **NOTE THAT** The ActivityPub spec [says](https://www.w3.org/TR/activitypub/#obj)
051
052 > Implementers SHOULD include the ActivityPub context in their object
053 > definitions
054
055 but in samples found in the wild they typically don't."
056 ([x]
057 (and (map? x) (:type x) true))
058 ([x severity]
059 (empty? (filter-severity (object-faults x) severity))))
060
061 (defn persistent-object?
062 "`true` iff `x` is a persistent object.
063
064 Transient objects in ActivityPub are not required to have an `id` key, but persistent
065 ones must have a key, and it must be an IRI (but normally a URI)."
066 [x]
067 (try
068 (and (object? x) (uri? (URI. (:id x))))
069 (catch URISyntaxException _ false)))
070
071 ;; (persistent-object? {:type "test" :id "https://mastodon.scot/@barfilfarm"})
072
073 (def ^:const actor-types
074 "The set of types we will accept as actors.
075
076 There's an [explicit set of allowed actor types]
077 (https://www.w3.org/TR/activitystreams-vocabulary/#actor-types)."
078 #{"Application"
079 "Group"
080 "Organization"
081 "Person"
082 "Service"})
083
084 (defmacro actor-type?
085 "Return `true` iff the `x` is a recognised actor type, else `false`."
086 [^String x]
087 `(if (actor-types ~x) true false))
088
089 ;; (actor-type? "Group")
090
091 (def ^:const verb-types
092 "The set of types we will accept as verbs.
093
094 There's an [explicit set of allowed verb types]
095 (https://www.w3.org/TR/activitystreams-vocabulary/#activity-types)."
096 #{"Accept" "Add" "Announce" "Arrive" "Block" "Create" "Delete" "Dislike"
097 "Flag" "Follow" "Ignore" "Invite" "Join" "Leave" "Like" "Listen" "Move"
098 "Offer" "Question" "Reject" "Read" "Remove" "TentativeAccept"
099 "TentativeReject" "Travel" "Undo" "Update" "View"})
100
101 (defmacro verb-type?
102 ;; TODO: better as a macro
103 [^String x]
104 `(if (verb-types ~x) true false))
105
106
107 (defn actor?
108 "Returns `true` if `x` quacks like an actor, else false.
109
110 **NOTE THAT** [Section 4.1 of the spec]
111 (https://www.w3.org/TR/activitypub/#actor-objects) says explicitly that
112
113 > Actor objects MUST have, in addition to the properties mandated by 3.1 Object Identifiers, the following properties:
114 >
115 > inbox
116 > A reference to an [ActivityStreams] OrderedCollection comprised of all the messages received by the actor; see 5.2 Inbox.
117 > outbox
118 > An [ActivityStreams] OrderedCollection comprised of all the messages produced by the actor; see 5.1 Outbox.
119
120 However, none of the provided examples in the [activitystreams-test-documents repository]() does in fact have these properties"
121 [x]
122 (and
123 (object? x)
124 (has-context? x)
125 (uri? (URI. (:inbox x)))
126 (uri? (URI. (:outbox x)))
127 (actor-type? (:type x))
128 true))
129
130 (defn actor-or-uri?
131 "`true` if `x` is either a URI or an actor.
132
133 **TODO**: I need to decide about whether to reify referenced objects
134 before validation or after. After reification, every reference to an actor
135 *must be* to an actor object, but before, may only be to a URI pointing to
136 one."
137 [x]
138 (and
139 (cond (string? x) (uri? (URI. x))
140 :else (actor? x))
141 true))
142
143 (defn activity?
144 "`true` iff `x` quacks like an activity, else false."
145 [x]
146 (try
147 (and (object? x)
148 (has-context? x)
149 (string? (:summary x))
150 (actor-or-uri? (:actor x))
151 (verb-type? (:type x))
152 (or (object? (:object x)) (uri? (URI. (:object x))))
153 true)
154 (catch URISyntaxException _ false)))
155
156 (defn link?
157 "`true` iff `x` quacks like a link, else false."
158 [x]
159 (and (object? x)
160 (= (:type x) "Link")
161 (uri? (URI. (:href x)))
162 true))
163
164 (defn link-or-uri?
165 "`true` iff `x` is either a URI or a link, else false.
166
167 There are several points in the specification where e.g. the `:image`
168 property (if present) may be either a link or a URI."
169 [x]
170 (and
171 (cond (string? x) (uri? (URI. x))
172 :else (link? x))
173 true))
174
175 (defn collection?
176 "`true` iff `x` quacks like a collection of type `object-type`, else `false`.
177
178 With one argument, will recognise plain collections and ordered collections,
179 but (currently) not collection pages."
180 ([x ^String object-type]
181 (let [items (or (:items x) (:orderedItems x))]
182 (and
183 (cond
184 (:items x) (nil? (:orderedItems x))
185 (:orderedItems x) (nil? (:items x)) ;; can't have both properties
186 (integer? (:totalItems x)) true ;; can have neither, provided it has totalItems.
187 :else false)
188 (object? x)
189 (= (:type x) object-type)
190 (if items
191 (and (coll? items)
192 (every? object? items) ;; if there are items, they must form a
193 ;; collection of objects.
194 true)
195 true) ;; but it's OK if there aren't.
196 true)
197 ;; test for totalItems not done here, because collection pages don't
198 ;; have it.
199 ))
200 ([x]
201 (and
202 (or (collection? x "Collection")
203 (collection? x "OrderedCollection"))
204 (integer? (:totalItems x))
205 true)))
206
207 (defn unordered-collection?
208 "`true` iff `x` quacks like an unordered collection, else `false`."
209 [x]
210 (and (collection? x "Collection") (integer? (:totalItems x)) true))
211
212 (defn ordered-collection?
213 "`true` iff `x` quacks like an ordered collection, else `false`."
214 [x]
215 (and (collection? x "OrderedCollection") (integer? (:totalItems x)) true))
216
217 (defn collection-page?
218 "`true` iff `x` quacks like a page in a paged collection, else `false`."
219 [x]
220 (collection? x "CollectionPage"))
221
222 (defn ordered-collection-page?
223 "`true` iff `x` quacks like a page in an ordered paged collection, else `false`."
224 [x]
225 (collection? x "OrderedCollectionPage"))
226
227