Removed Jahfer's clj-activitypub library.
Will, but have not yet, add a dependency on it.
This commit is contained in:
parent
09ebdafff5
commit
c18a0a5b8e
0
doc/Per-User Database
Normal file
0
doc/Per-User Database
Normal file
|
@ -1,3 +0,0 @@
|
|||
# NOTE
|
||||
|
||||
Files in this directory are copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub). If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.
|
|
@ -1,150 +0,0 @@
|
|||
(ns clj-activitypub.core
|
||||
"copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub).
|
||||
If and when Jahfer issues a release of that library, this directory will be deleted and a
|
||||
dependency on that library will be added to the project."
|
||||
(:require [clj-activitypub.internal.crypto :as crypto]
|
||||
[clj-activitypub.internal.thread-cache :as thread-cache]
|
||||
[clj-activitypub.internal.http-util :as http]
|
||||
[clj-http.client :as client]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn config
|
||||
"Creates hash of computed data relevant for most ActivityPub utilities."
|
||||
[{:keys [domain username username-route public-key private-key]
|
||||
:or {username-route "/users/"
|
||||
public-key nil
|
||||
private-key nil}}]
|
||||
(let [base-url (str "https://" domain)]
|
||||
{:domain domain
|
||||
:base-url base-url
|
||||
:username username
|
||||
:user-id (str base-url username-route username)
|
||||
:public-key public-key
|
||||
:private-key (when private-key
|
||||
(crypto/private-key private-key))}))
|
||||
|
||||
(defn parse-account
|
||||
"Given an ActivityPub handle (e.g. @jahfer@mastodon.social), produces
|
||||
a map containing {:domain ... :username ...}."
|
||||
[handle]
|
||||
(let [[username domain] (filter #(not (str/blank? %))
|
||||
(str/split handle #"@"))]
|
||||
{:domain domain :username username}))
|
||||
|
||||
(def ^:private user-cache (thread-cache/make))
|
||||
(defn fetch-user
|
||||
"Fetches the customer account details located at user-id from a remote
|
||||
server. Will return cached results if they exist in memory."
|
||||
[user-id]
|
||||
((:get-v user-cache)
|
||||
user-id
|
||||
#(:body
|
||||
(client/get user-id {:as :json-string-keys
|
||||
:throw-exceptions false
|
||||
:ignore-unknown-host? true
|
||||
:headers {"Accept" "application/activity+json"}}))))
|
||||
|
||||
(defn actor
|
||||
"Accepts a config, and returns a map in the form expected by the ActivityPub
|
||||
spec. See https://www.w3.org/TR/activitypub/#actor-objects for reference."
|
||||
[{:keys [user-id username public-key]}]
|
||||
{"@context" ["https://www.w3.org/ns/activitystreams"
|
||||
"https://w3id.org/security/v1"]
|
||||
:id user-id
|
||||
:type "Person"
|
||||
:preferredUsername username
|
||||
:inbox (str user-id "/inbox")
|
||||
:outbox (str user-id "/outbox")
|
||||
:publicKey {:id (str user-id "#main-key")
|
||||
:owner user-id
|
||||
:publicKeyPem (or public-key "")}})
|
||||
|
||||
(def signature-headers ["(request-target)" "host" "date" "digest"])
|
||||
|
||||
(defn- str-for-signature [headers]
|
||||
(let [headers-xf (reduce-kv
|
||||
(fn [m k v]
|
||||
(assoc m (str/lower-case k) v)) {} headers)]
|
||||
(->> signature-headers
|
||||
(select-keys headers-xf)
|
||||
(reduce-kv (fn [coll k v] (conj coll (str k ": " v))) [])
|
||||
(interpose "\n")
|
||||
(apply str))))
|
||||
|
||||
(defn gen-signature-header
|
||||
"Generates a HTTP Signature string based on the provided map of headers."
|
||||
[config headers]
|
||||
(let [{:keys [user-id private-key]} config
|
||||
string-to-sign (str-for-signature headers)
|
||||
signature (crypto/base64-encode (crypto/sign string-to-sign private-key))
|
||||
sig-header-keys {"keyId" user-id
|
||||
"headers" (str/join " " signature-headers)
|
||||
"signature" signature}]
|
||||
(->> sig-header-keys
|
||||
(reduce-kv (fn [m k v]
|
||||
(conj m (str k "=" "\"" v "\""))) [])
|
||||
(interpose ",")
|
||||
(apply str))))
|
||||
|
||||
(defn auth-headers
|
||||
"Given a config and request map of {:body ... :headers ...}, returns the
|
||||
original set of headers with Signature and Digest attributes appended."
|
||||
[config {:keys [body headers]}]
|
||||
(let [digest (http/digest body)
|
||||
h (-> headers
|
||||
(assoc "Digest" digest)
|
||||
(assoc "(request-target)" "post /inbox"))]
|
||||
(assoc headers
|
||||
"Signature" (gen-signature-header config h)
|
||||
"Digest" digest)))
|
||||
|
||||
(defmulti obj
|
||||
"Produces a map representing an ActivityPub object which can be serialized
|
||||
directly to JSON in the form expected by the ActivityStreams 2.0 spec.
|
||||
See https://www.w3.org/TR/activitystreams-vocabulary/ for reference."
|
||||
(fn [_config object-data] (:type object-data)))
|
||||
|
||||
(defmethod obj :note
|
||||
[{:keys [user-id]}
|
||||
{:keys [id published inReplyTo content to]
|
||||
:or {published (http/date)
|
||||
inReplyTo ""
|
||||
to "https://www.w3.org/ns/activitystreams#Public"}}]
|
||||
{"id" (str user-id "/notes/" id)
|
||||
"type" "Note"
|
||||
"published" published
|
||||
"attributedTo" user-id
|
||||
"inReplyTo" inReplyTo
|
||||
"content" content
|
||||
"to" to})
|
||||
|
||||
(defmulti activity
|
||||
"Produces a map representing an ActivityPub activity which can be serialized
|
||||
directly to JSON in the form expected by the ActivityStreams 2.0 spec.
|
||||
See https://www.w3.org/TR/activitystreams-vocabulary/ for reference."
|
||||
(fn [_config activity-type _data] activity-type))
|
||||
|
||||
(defmethod activity :create [{:keys [user-id]} _ data]
|
||||
{"@context" ["https://www.w3.org/ns/activitystreams"
|
||||
"https://w3id.org/security/v1"]
|
||||
"type" "Create"
|
||||
"actor" user-id
|
||||
"object" data})
|
||||
|
||||
(defmethod activity :delete [{:keys [user-id]} _ data]
|
||||
{"@context" ["https://www.w3.org/ns/activitystreams"
|
||||
"https://w3id.org/security/v1"]
|
||||
"type" "Delete"
|
||||
"actor" user-id
|
||||
"object" data})
|
||||
|
||||
(defn with-config
|
||||
"Returns curried forms of the #activity and #obj multimethods in the form
|
||||
{:activity ... :obj ...}, with the initial parameter set to config."
|
||||
[config]
|
||||
(let [f (juxt
|
||||
#(partial activity %)
|
||||
#(partial obj %))
|
||||
[activity-fn obj-fn] (f config)]
|
||||
{:activity activity-fn
|
||||
:obj obj-fn}))
|
|
@ -1,39 +0,0 @@
|
|||
(ns clj-activitypub.internal.crypto
|
||||
"copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub).
|
||||
If and when Jahfer issues a release of that library, this directory will be deleted and a
|
||||
dependency on that library will be added to the project."
|
||||
(:require [clojure.java.io :as io])
|
||||
(:import (java.util Base64)
|
||||
(java.security MessageDigest SecureRandom Signature)))
|
||||
|
||||
(java.security.Security/addProvider
|
||||
(org.bouncycastle.jce.provider.BouncyCastleProvider.))
|
||||
|
||||
(defn- keydata [reader]
|
||||
(->> reader
|
||||
(org.bouncycastle.openssl.PEMParser.)
|
||||
(.readObject)))
|
||||
|
||||
(defn- pem-string->key-pair [string]
|
||||
(let [kd (keydata (io/reader (.getBytes string)))]
|
||||
(.getKeyPair (org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.) kd)))
|
||||
|
||||
(defn private-key [private-pem-str]
|
||||
(-> private-pem-str
|
||||
(pem-string->key-pair)
|
||||
(.getPrivate)))
|
||||
|
||||
(defn base64-encode [bytes]
|
||||
(.encodeToString (Base64/getEncoder) bytes))
|
||||
|
||||
(defn sha256-base64 [data]
|
||||
(let [digest (.digest (MessageDigest/getInstance "SHA-256") (.getBytes data))]
|
||||
(base64-encode digest)))
|
||||
|
||||
(defn sign [data private-key]
|
||||
(let [bytes (.getBytes data)
|
||||
signer (doto (Signature/getInstance "SHA256withRSA")
|
||||
(.initSign private-key (SecureRandom.))
|
||||
(.update bytes))]
|
||||
(.sign signer)))
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
(ns clj-activitypub.internal.http-util
|
||||
"copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub).
|
||||
If and when Jahfer issues a release of that library, this directory will be deleted and a
|
||||
dependency on that library will be added to the project."
|
||||
(:require [clj-activitypub.internal.crypto :as crypto])
|
||||
(:import (java.net URLEncoder)
|
||||
(java.time OffsetDateTime ZoneOffset)
|
||||
(java.time.format DateTimeFormatter)))
|
||||
|
||||
(defn encode-url-params [params]
|
||||
(->> params
|
||||
(reduce-kv
|
||||
(fn [coll k v]
|
||||
(conj coll
|
||||
(str (URLEncoder/encode (name k)) "=" (URLEncoder/encode (str v)))))
|
||||
[])
|
||||
(interpose "&")
|
||||
(apply str)))
|
||||
|
||||
(defn date []
|
||||
(-> (OffsetDateTime/now (ZoneOffset/UTC))
|
||||
(.format DateTimeFormatter/RFC_1123_DATE_TIME)))
|
||||
|
||||
(defn digest
|
||||
"Accepts body from HTTP request and generates string
|
||||
for use in HTTP `Digest` request header."
|
||||
[body]
|
||||
(str "sha-256=" (crypto/sha256-base64 body)))
|
|
@ -1,47 +0,0 @@
|
|||
(ns clj-activitypub.internal.thread-cache
|
||||
"copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub).
|
||||
If and when Jahfer issues a release of that library, this directory will be deleted and a
|
||||
dependency on that library will be added to the project.")
|
||||
|
||||
(defn- current-time
|
||||
"Returns current time using UNIX epoch."
|
||||
[]
|
||||
(System/currentTimeMillis))
|
||||
|
||||
(defn- update-read-at [store k v]
|
||||
(dosync
|
||||
(commute store assoc k
|
||||
(merge v {:read-at (current-time)}))))
|
||||
|
||||
(defn make
|
||||
"Creates a thread-local cache."
|
||||
([] (make false))
|
||||
([cache-if-nil]
|
||||
(let [store (ref {})]
|
||||
(letfn [(cache-kv ([k v]
|
||||
(dosync
|
||||
(commute store assoc k
|
||||
{:write-at (current-time)
|
||||
:read-at (current-time)
|
||||
:value v})
|
||||
v)))
|
||||
(get-v ([k]
|
||||
(when-let [data (get @store k)]
|
||||
(update-read-at store k data)
|
||||
(:value data)))
|
||||
([k compute-fn]
|
||||
(let [storage @store]
|
||||
(if (contains? storage k)
|
||||
(get-v k)
|
||||
(let [v (compute-fn)]
|
||||
(when (or (not (nil? v)) cache-if-nil)
|
||||
(cache-kv k v)
|
||||
(get-v k)))))))
|
||||
(lru ([]
|
||||
(mapv
|
||||
(fn [[k v]] [k (:value v)])
|
||||
(sort-by #(-> % val :read-at) < @store))))]
|
||||
{:cache-kv cache-kv
|
||||
:get-v get-v
|
||||
:cache-if-nil cache-if-nil
|
||||
:lru lru}))))
|
|
@ -1,35 +0,0 @@
|
|||
(ns clj-activitypub.webfinger
|
||||
"copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub).
|
||||
If and when Jahfer issues a release of that library, this directory will be deleted and a
|
||||
dependency on that library will be added to the project."
|
||||
(:require [clj-http.client :as client]
|
||||
[clj-activitypub.internal.http-util :as http]
|
||||
[clj-activitypub.internal.thread-cache :as thread-cache]))
|
||||
|
||||
(def remote-uri-path "/.well-known/webfinger")
|
||||
|
||||
(defn- resource-str [domain username]
|
||||
(str "acct:" username "@" domain))
|
||||
|
||||
(defn resource-url
|
||||
"Builds a URL pointing to the user's account on the remote server."
|
||||
[domain username & [params]]
|
||||
(let [resource (resource-str domain username)
|
||||
query-str (http/encode-url-params (merge params {:resource resource}))]
|
||||
(str "https://" domain remote-uri-path "?" query-str)))
|
||||
|
||||
(def ^:private user-id-cache
|
||||
(thread-cache/make))
|
||||
|
||||
(defn fetch-user-id
|
||||
"Follows the webfinger request to a remote domain, retrieving the ID of the requested
|
||||
account. Typically returns a string in the form of a URL."
|
||||
[domain username]
|
||||
((:get-v user-id-cache)
|
||||
(str domain "@" username) ;; cache key
|
||||
(fn []
|
||||
(let [response (some-> (resource-url domain username {:rel "self"})
|
||||
(client/get {:as :json :throw-exceptions false :ignore-unknown-host? true}))]
|
||||
(some->> response :body :links
|
||||
(some #(when (= (:type %) "application/activity+json") %))
|
||||
:href)))))
|
Loading…
Reference in a new issue