Webfinger in OCaml via RFC7033

Initial implementation of RFC 7033 WebFinger and RFC 7565 acct URI

Implements the WebFinger protocol (RFC 7033) for discovering information
about resources identified by URIs, and the acct URI scheme (RFC 7565)
for identifying user accounts at service providers.

Features:
- Acct module: type-safe parsing/construction of acct URIs with proper
percent-encoding per RFC 7565
- JRD module: JSON Resource Descriptor encoding/decoding using jsont
- Link module: link relation handling per RFC 7033 Section 4.4.4
- HTTP client: Eio-based WebFinger queries using requests library
- CLI tool: webfinger command for performing lookups

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+3353
+17
.gitignore
··· 1 + # OCaml build artifacts 2 + _build/ 3 + *.install 4 + *.merlin 5 + 6 + # Third-party sources (fetch locally with opam source) 7 + third_party/ 8 + 9 + # Editor and OS files 10 + .DS_Store 11 + *.swp 12 + *~ 13 + .vscode/ 14 + .idea/ 15 + 16 + # Opam local switch 17 + _opam/
+1
.ocamlformat
··· 1 + version=0.28.1
+53
.tangled/workflows/build.yml
··· 1 + when: 2 + - event: ["push", "pull_request"] 3 + branch: ["main"] 4 + 5 + engine: nixery 6 + 7 + dependencies: 8 + nixpkgs: 9 + - shell 10 + - stdenv 11 + - findutils 12 + - binutils 13 + - libunwind 14 + - ncurses 15 + - opam 16 + - git 17 + - gawk 18 + - gnupatch 19 + - gnum4 20 + - gnumake 21 + - gnutar 22 + - gnused 23 + - gnugrep 24 + - diffutils 25 + - gzip 26 + - bzip2 27 + - gcc 28 + - ocaml 29 + - pkg-config 30 + 31 + steps: 32 + - name: opam 33 + command: | 34 + opam init --disable-sandboxing -a -y 35 + - name: repo 36 + command: | 37 + opam repo add aoah https://tangled.org/anil.recoil.org/aoah-opam-repo.git 38 + - name: switch 39 + command: | 40 + opam install . --confirm-level=unsafe-yes --deps-only 41 + - name: build 42 + command: | 43 + opam exec -- dune build 44 + - name: switch-test 45 + command: | 46 + opam install . --confirm-level=unsafe-yes --deps-only --with-test 47 + - name: test 48 + command: | 49 + opam exec -- dune runtest --verbose 50 + - name: doc 51 + command: | 52 + opam install -y odoc 53 + opam exec -- dune build @doc
+15
LICENSE.md
··· 1 + ISC License 2 + 3 + Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org> 4 + 5 + Permission to use, copy, modify, and distribute this software for any 6 + purpose with or without fee is hereby granted, provided that the above 7 + copyright notice and this permission notice appear in all copies. 8 + 9 + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+96
README.md
··· 1 + # webfinger - RFC 7033 WebFinger and RFC 7565 acct URI 2 + 3 + An OCaml implementation of the WebFinger protocol (RFC 7033) and the acct URI scheme (RFC 7565) for discovering information about resources identified by URIs. 4 + 5 + ## Key Features 6 + 7 + - **RFC 7033 WebFinger**: Complete implementation of the WebFinger protocol 8 + - **RFC 7565 acct URIs**: Type-safe parsing and construction of acct URIs with proper percent-encoding 9 + - **Type-safe JRD**: JSON Resource Descriptor encoding/decoding using jsont 10 + - **Eio-based HTTP client**: Async HTTP queries using the requests library 11 + - **Command-line tool**: CLI for performing WebFinger lookups 12 + 13 + ## Usage 14 + 15 + ### Library Usage 16 + 17 + ```ocaml 18 + Eio_main.run @@ fun env -> 19 + Eio.Switch.run @@ fun sw -> 20 + let session = Requests.create ~sw env in 21 + 22 + (* Parse and query an acct URI *) 23 + let acct = Webfinger.Acct.of_string_exn "acct:user@example.com" in 24 + match Webfinger.query_acct session acct () with 25 + | Ok jrd -> 26 + Format.printf "%a@." Webfinger.Jrd.pp jrd; 27 + begin match Webfinger.Jrd.find_link ~rel:"self" jrd with 28 + | Some link -> 29 + Format.printf "ActivityPub: %s@." 30 + (Option.get (Webfinger.Link.href link)) 31 + | None -> () 32 + end 33 + | Error e -> 34 + Format.eprintf "Error: %a@." Webfinger.pp_error e 35 + ``` 36 + 37 + ### Working with acct URIs (RFC 7565) 38 + 39 + ```ocaml 40 + (* Create an acct URI *) 41 + let acct = Webfinger.Acct.make ~userpart:"user" ~host:"example.com" in 42 + Format.printf "%a@." Webfinger.Acct.pp acct; 43 + (* Output: acct:user@example.com *) 44 + 45 + (* Handle email addresses as userparts (@ is percent-encoded) *) 46 + let acct = Webfinger.Acct.make 47 + ~userpart:"juliet@capulet.example" 48 + ~host:"shoppingsite.example" in 49 + Format.printf "%s@." (Webfinger.Acct.to_string acct); 50 + (* Output: acct:juliet%40capulet.example@shoppingsite.example *) 51 + 52 + (* Parse and extract components *) 53 + let acct = Webfinger.Acct.of_string_exn 54 + "acct:juliet%40capulet.example@shoppingsite.example" in 55 + Format.printf "User: %s, Host: %s@." 56 + (Webfinger.Acct.userpart acct) (* juliet@capulet.example *) 57 + (Webfinger.Acct.host acct) (* shoppingsite.example *) 58 + ``` 59 + 60 + ### Command-line Tool 61 + 62 + ```bash 63 + # Query a Mastodon user's WebFinger record 64 + webfinger acct:gargron@mastodon.social 65 + 66 + # Get only ActivityPub-related links 67 + webfinger --rel self acct:user@example.com 68 + 69 + # Output raw JSON for processing 70 + webfinger --json acct:user@example.com | jq . 71 + ``` 72 + 73 + ## Installation 74 + 75 + ``` 76 + opam install webfinger 77 + ``` 78 + 79 + ## Documentation 80 + 81 + API documentation is available via: 82 + 83 + ``` 84 + opam install webfinger 85 + odig doc webfinger 86 + ``` 87 + 88 + ## References 89 + 90 + - [RFC 7033](https://datatracker.ietf.org/doc/html/rfc7033) - WebFinger 91 + - [RFC 7565](https://datatracker.ietf.org/doc/html/rfc7565) - The 'acct' URI Scheme 92 + - [RFC 6415](https://datatracker.ietf.org/doc/html/rfc6415) - Web Host Metadata 93 + 94 + ## License 95 + 96 + ISC
+4
bin/dune
··· 1 + (executables 2 + (public_names webfinger) 3 + (names webfinger_cli) 4 + (libraries webfinger eio_main cmdliner logs logs.cli logs.fmt fmt.cli fmt.tty jsont jsont.bytesrw))
+122
bin/webfinger_cli.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** WebFinger command-line tool. 7 + 8 + A CLI for performing WebFinger (RFC 7033) queries against any host. *) 9 + 10 + open Cmdliner 11 + 12 + (** {1 Command-line Arguments} *) 13 + 14 + let resource = 15 + let doc = "Resource URI to query (e.g., acct:user@example.com)" in 16 + Arg.(required & pos 0 (some string) None & info [] ~docv:"RESOURCE" ~doc) 17 + 18 + let rels = 19 + let doc = "Filter links by relation type (can be specified multiple times)" in 20 + Arg.(value & opt_all string [] & info ["r"; "rel"] ~docv:"REL" ~doc) 21 + 22 + let json_output = 23 + let doc = "Output raw JSON instead of formatted text" in 24 + Arg.(value & flag & info ["j"; "json"] ~doc) 25 + 26 + let show_links_only = 27 + let doc = "Show only links, not subject/aliases/properties" in 28 + Arg.(value & flag & info ["l"; "links-only"] ~doc) 29 + 30 + (** {1 Logging Setup} *) 31 + 32 + let setup_log style_renderer level = 33 + Fmt_tty.setup_std_outputs ?style_renderer (); 34 + Logs.set_level level; 35 + Logs.set_reporter (Logs_fmt.reporter ()); 36 + (* Return whether we should be quiet (log level is None) *) 37 + Option.is_none level 38 + 39 + (** {1 Output Formatting} *) 40 + 41 + let pp_link_compact ppf link = 42 + let href = Option.value ~default:"(no href)" (Webfinger.Link.href link) in 43 + Format.fprintf ppf "@[<h>%s@ ->@ %s" (Webfinger.Link.rel link) href; 44 + Option.iter (fun t -> Format.fprintf ppf "@ [%s]" t) (Webfinger.Link.type_ link); 45 + Format.fprintf ppf "@]" 46 + 47 + let pp_jrd_compact ppf jrd = 48 + Option.iter (fun s -> Format.fprintf ppf "@[<v>Subject: %s@]@," s) (Webfinger.Jrd.subject jrd); 49 + let aliases = Webfinger.Jrd.aliases jrd in 50 + if aliases <> [] then begin 51 + Format.fprintf ppf "@[<v>Aliases:@,"; 52 + List.iter (fun a -> Format.fprintf ppf " %s@," a) aliases; 53 + Format.fprintf ppf "@]" 54 + end; 55 + let props = Webfinger.Jrd.properties jrd in 56 + if props <> [] then begin 57 + Format.fprintf ppf "@[<v>Properties:@,"; 58 + List.iter (fun (k, v) -> 59 + let v = Option.value ~default:"null" v in 60 + Format.fprintf ppf " %s: %s@," k v 61 + ) props; 62 + Format.fprintf ppf "@]" 63 + end; 64 + let links = Webfinger.Jrd.links jrd in 65 + if links <> [] then begin 66 + Format.fprintf ppf "@[<v>Links:@,"; 67 + List.iter (fun link -> Format.fprintf ppf " %a@," pp_link_compact link) links; 68 + Format.fprintf ppf "@]" 69 + end 70 + 71 + let pp_links_only ppf jrd = 72 + List.iter (fun link -> Format.fprintf ppf "%a@," pp_link_compact link) (Webfinger.Jrd.links jrd) 73 + 74 + (** {1 Main Command} *) 75 + 76 + let run quiet resource rels json_output links_only = 77 + Eio_main.run @@ fun env -> 78 + Eio.Switch.run @@ fun sw -> 79 + let session = Requests.create ~sw env in 80 + match Webfinger.query session ~resource ~rels () with 81 + | Error e -> 82 + if not quiet then Format.eprintf "Error: %a@." Webfinger.pp_error e; 83 + `Error (false, Webfinger.error_to_string e) 84 + | Ok jrd -> 85 + if not quiet then begin 86 + if json_output then 87 + Format.printf "%s@." (Webfinger.Jrd.to_string jrd) 88 + else if links_only then 89 + Format.printf "%a" pp_links_only jrd 90 + else 91 + Format.printf "%a" pp_jrd_compact jrd 92 + end; 93 + `Ok () 94 + 95 + let run_term = 96 + let quiet = Term.(const setup_log $ Fmt_cli.style_renderer () $ Logs_cli.level ()) in 97 + Term.(ret (const run $ quiet $ resource $ rels $ json_output $ show_links_only)) 98 + 99 + (** {1 Command Definition} *) 100 + 101 + let cmd = 102 + let doc = "Query WebFinger (RFC 7033) resources" in 103 + let man = [ 104 + `S Manpage.s_description; 105 + `P "$(tname) queries WebFinger servers to discover information about \ 106 + resources identified by URIs. This is commonly used for discovering \ 107 + ActivityPub profiles, OpenID Connect providers, and other federated \ 108 + services."; 109 + `S Manpage.s_examples; 110 + `P "Query a Mastodon user's WebFinger record:"; 111 + `Pre " $(tname) acct:gargron@mastodon.social"; 112 + `P "Get only ActivityPub-related links:"; 113 + `Pre " $(tname) --rel self acct:user@example.com"; 114 + `P "Output raw JSON for processing:"; 115 + `Pre " $(tname) --json acct:user@example.com | jq ."; 116 + `S Manpage.s_see_also; 117 + `P "RFC 7033 - WebFinger: $(b,https://datatracker.ietf.org/doc/html/rfc7033)"; 118 + ] in 119 + let info = Cmd.info "webfinger" ~version:"0.1.0" ~doc ~man in 120 + Cmd.v info run_term 121 + 122 + let () = exit (Cmd.eval cmd)
+1
dune
··· 1 + (data_only_dirs spec)
+35
dune-project
··· 1 + (lang dune 3.20) 2 + 3 + (name webfinger) 4 + 5 + (generate_opam_files true) 6 + 7 + (license ISC) 8 + (authors "Anil Madhavapeddy") 9 + (homepage "https://tangled.org/@anil.recoil.org/ocaml-webfinger") 10 + (maintainers "Anil Madhavapeddy <anil@recoil.org>") 11 + (bug_reports "https://tangled.org/@anil.recoil.org/ocaml-webfinger/issues") 12 + (maintenance_intent "(latest)") 13 + 14 + (package 15 + (name webfinger) 16 + (synopsis "RFC 7033 WebFinger and RFC 7565 acct URI scheme for OCaml") 17 + (description 18 + "A complete implementation of RFC 7033 (WebFinger) and RFC 7565 (acct URI scheme) 19 + for discovering information about resources identified by URIs. Includes type-safe 20 + JSON Resource Descriptor (JRD) encoding/decoding using jsont, an Eio-based HTTP 21 + client using the requests library, and a command-line tool for WebFinger lookups.") 22 + (depends 23 + (ocaml (>= 5.2.0)) 24 + (dune (>= 3.0)) 25 + (jsont (>= 0.1.0)) 26 + (jsont-bytesrw (>= 0.1.0)) 27 + (eio (>= 1.0)) 28 + (eio_main (>= 1.0)) 29 + (requests (>= 0.1.0)) 30 + (uri (>= 4.0.0)) 31 + (cmdliner (>= 1.2.0)) 32 + (logs (>= 0.7.0)) 33 + (fmt (>= 0.9.0)) 34 + (odoc :with-doc) 35 + (alcotest :with-test)))
+4
lib/dune
··· 1 + (library 2 + (name webfinger) 3 + (public_name webfinger) 4 + (libraries jsont jsont.bytesrw uri eio requests logs))
+334
lib/webfinger.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** RFC 7033 WebFinger and RFC 7565 acct URI scheme. 7 + 8 + This module implements the WebFinger protocol as specified in 9 + {{:https://datatracker.ietf.org/doc/html/rfc7033}RFC 7033}, providing 10 + type-safe JSON Resource Descriptor (JRD) encoding/decoding and an 11 + HTTP client for WebFinger queries. It also implements the acct URI 12 + scheme as specified in {{:https://datatracker.ietf.org/doc/html/rfc7565}RFC 7565}. 13 + 14 + {2 References} 15 + {ul 16 + {- {{:https://datatracker.ietf.org/doc/html/rfc7033}RFC 7033} - WebFinger} 17 + {- {{:https://datatracker.ietf.org/doc/html/rfc7565}RFC 7565} - The 'acct' URI Scheme} 18 + {- {{:https://datatracker.ietf.org/doc/html/rfc6415}RFC 6415} - Web Host Metadata}} *) 19 + 20 + let src = Logs.Src.create "webfinger" ~doc:"WebFinger Protocol" 21 + module Log = (val Logs.src_log src : Logs.LOG) 22 + 23 + (** {1 Error Types} *) 24 + 25 + type error = 26 + | Invalid_resource of string 27 + | Http_error of { status : int; body : string } 28 + | Json_error of string 29 + | Https_required 30 + | Not_found 31 + 32 + let pp_error ppf = function 33 + | Invalid_resource s -> Format.fprintf ppf "Invalid resource: %s" s 34 + | Http_error { status; body } -> Format.fprintf ppf "HTTP error %d: %s" status body 35 + | Json_error s -> Format.fprintf ppf "JSON parse error: %s" s 36 + | Https_required -> Format.fprintf ppf "WebFinger requires HTTPS" 37 + | Not_found -> Format.fprintf ppf "Resource not found" 38 + 39 + let error_to_string e = Format.asprintf "%a" pp_error e 40 + 41 + exception Webfinger_error of error 42 + 43 + let raise_error e = raise (Webfinger_error e) 44 + 45 + (** {1 Acct Module} *) 46 + 47 + module Acct = struct 48 + type t = { 49 + userpart : string; 50 + host : string; 51 + } 52 + 53 + let make ~userpart ~host = 54 + if userpart = "" then invalid_arg "Acct.make: userpart cannot be empty"; 55 + if host = "" then invalid_arg "Acct.make: host cannot be empty"; 56 + { userpart; host } 57 + 58 + (** Percent-encode a userpart per RFC 7565. 59 + 60 + Per RFC 7565/RFC 3986, unreserved and sub-delims are allowed unencoded. 61 + We use Uri.pct_encode with a custom component that encodes @ (which is 62 + not in sub-delims) but allows the standard unreserved and sub-delims. *) 63 + let pct_encode_userpart s = 64 + (* Uri.pct_encode with `Userinfo encodes @ but we need it for our format *) 65 + Uri.pct_encode ~component:`Userinfo s 66 + 67 + let of_string s = 68 + if not (String.starts_with ~prefix:"acct:" s) then 69 + Error (Invalid_resource "URI must start with 'acct:'") 70 + else 71 + let rest = String.sub s 5 (String.length s - 5) in 72 + match String.rindex_opt rest '@' with 73 + | None -> 74 + Error (Invalid_resource "acct URI must contain '@' separating userpart and host") 75 + | Some idx -> 76 + let userpart_encoded = String.sub rest 0 idx in 77 + let host = String.sub rest (idx + 1) (String.length rest - idx - 1) in 78 + if userpart_encoded = "" then 79 + Error (Invalid_resource "userpart cannot be empty") 80 + else if host = "" then 81 + Error (Invalid_resource "host cannot be empty") 82 + else 83 + let userpart = Uri.pct_decode userpart_encoded in 84 + Ok { userpart; host = String.lowercase_ascii host } 85 + 86 + let of_string_exn s = 87 + match of_string s with 88 + | Ok acct -> acct 89 + | Error e -> raise_error e 90 + 91 + let to_string t = 92 + Printf.sprintf "acct:%s@%s" (pct_encode_userpart t.userpart) t.host 93 + 94 + let userpart t = t.userpart 95 + let host t = t.host 96 + 97 + let equal a b = 98 + (* Per RFC 3986 Section 6.2.2: case normalization (host) and 99 + percent-encoding normalization (userpart decoded for comparison) *) 100 + a.userpart = b.userpart && 101 + String.lowercase_ascii a.host = String.lowercase_ascii b.host 102 + 103 + let pp ppf t = 104 + Format.fprintf ppf "%s" (to_string t) 105 + end 106 + 107 + (** {1 Internal helpers} *) 108 + 109 + let pp_properties ppf props = 110 + List.iter (fun (uri, value) -> 111 + match value with 112 + | Some v -> Format.fprintf ppf " %s: %s@," uri v 113 + | None -> Format.fprintf ppf " %s: null@," uri 114 + ) props 115 + 116 + (** {1 Internal JSON helpers} *) 117 + 118 + module String_map = Map.Make(String) 119 + 120 + let properties_jsont : (string * string option) list Jsont.t = 121 + let inner = 122 + Jsont.Object.map ~kind:"Properties" Fun.id 123 + |> Jsont.Object.keep_unknown (Jsont.Object.Mems.string_map (Jsont.option Jsont.string)) ~enc:Fun.id 124 + |> Jsont.Object.finish 125 + in 126 + Jsont.map 127 + ~dec:(fun m -> List.of_seq (String_map.to_seq m)) 128 + ~enc:(fun l -> String_map.of_list l) 129 + inner 130 + 131 + let titles_jsont : (string * string) list Jsont.t = 132 + let inner = 133 + Jsont.Object.map ~kind:"Titles" Fun.id 134 + |> Jsont.Object.keep_unknown (Jsont.Object.Mems.string_map Jsont.string) ~enc:Fun.id 135 + |> Jsont.Object.finish 136 + in 137 + Jsont.map 138 + ~dec:(fun m -> List.of_seq (String_map.to_seq m)) 139 + ~enc:(fun l -> String_map.of_list l) 140 + inner 141 + 142 + (** {1 Link Module} *) 143 + 144 + module Link = struct 145 + type t = { 146 + rel : string; 147 + type_ : string option; 148 + href : string option; 149 + titles : (string * string) list; 150 + properties : (string * string option) list; 151 + } 152 + 153 + let make ~rel ?type_ ?href ?(titles = []) ?(properties = []) () = 154 + { rel; type_; href; titles; properties } 155 + 156 + let rel t = t.rel 157 + let type_ t = t.type_ 158 + let href t = t.href 159 + let titles t = t.titles 160 + let properties t = t.properties 161 + 162 + let title ?(lang = "und") t = 163 + match List.assoc_opt lang t.titles with 164 + | Some title -> Some title 165 + | None -> List.assoc_opt "und" t.titles 166 + 167 + let property ~uri t = List.assoc_opt uri t.properties |> Option.join 168 + 169 + let jsont = 170 + let make rel type_ href titles properties = 171 + { rel; type_; href; titles; properties } 172 + in 173 + Jsont.Object.map ~kind:"Link" make 174 + |> Jsont.Object.mem "rel" Jsont.string ~enc:(fun (l : t) -> l.rel) 175 + |> Jsont.Object.opt_mem "type" Jsont.string ~enc:(fun (l : t) -> l.type_) 176 + |> Jsont.Object.opt_mem "href" Jsont.string ~enc:(fun (l : t) -> l.href) 177 + |> Jsont.Object.mem "titles" titles_jsont ~dec_absent:[] ~enc_omit:(fun x -> x = []) ~enc:(fun (l : t) -> l.titles) 178 + |> Jsont.Object.mem "properties" properties_jsont ~dec_absent:[] ~enc_omit:(fun x -> x = []) ~enc:(fun (l : t) -> l.properties) 179 + |> Jsont.Object.skip_unknown 180 + |> Jsont.Object.finish 181 + 182 + let pp ppf t = 183 + Format.fprintf ppf "@[<v 2>Link:@,rel: %s@," t.rel; 184 + Option.iter (Format.fprintf ppf "type: %s@,") t.type_; 185 + Option.iter (Format.fprintf ppf "href: %s@,") t.href; 186 + if t.titles <> [] then begin 187 + Format.fprintf ppf "titles:@,"; 188 + List.iter (fun (lang, title) -> Format.fprintf ppf " %s: %s@," lang title) t.titles 189 + end; 190 + if t.properties <> [] then begin 191 + Format.fprintf ppf "properties:@,"; 192 + pp_properties ppf t.properties 193 + end; 194 + Format.fprintf ppf "@]" 195 + end 196 + 197 + (** {1 JRD Module} *) 198 + 199 + module Jrd = struct 200 + type t = { 201 + subject : string option; 202 + aliases : string list; 203 + properties : (string * string option) list; 204 + links : Link.t list; 205 + } 206 + 207 + let make ?subject ?(aliases = []) ?(properties = []) ?(links = []) () = 208 + { subject; aliases; properties; links } 209 + 210 + let subject t = t.subject 211 + let aliases t = t.aliases 212 + let properties t = t.properties 213 + let links t = t.links 214 + 215 + let find_link ~rel t = List.find_opt (fun l -> Link.rel l = rel) t.links 216 + let find_links ~rel t = List.filter (fun l -> Link.rel l = rel) t.links 217 + let property ~uri t = List.assoc_opt uri t.properties |> Option.join 218 + 219 + let jsont = 220 + let make subject aliases properties links = 221 + { subject; aliases; properties; links } 222 + in 223 + Jsont.Object.map ~kind:"JRD" make 224 + |> Jsont.Object.opt_mem "subject" Jsont.string ~enc:(fun (j : t) -> j.subject) 225 + |> Jsont.Object.mem "aliases" (Jsont.list Jsont.string) ~dec_absent:[] ~enc_omit:(fun x -> x = []) ~enc:(fun (j : t) -> j.aliases) 226 + |> Jsont.Object.mem "properties" properties_jsont ~dec_absent:[] ~enc_omit:(fun x -> x = []) ~enc:(fun (j : t) -> j.properties) 227 + |> Jsont.Object.mem "links" (Jsont.list Link.jsont) ~dec_absent:[] ~enc_omit:(fun x -> x = []) ~enc:(fun (j : t) -> j.links) 228 + |> Jsont.Object.skip_unknown 229 + |> Jsont.Object.finish 230 + 231 + let of_string s = 232 + Jsont_bytesrw.decode_string jsont s 233 + |> Result.map_error (fun e -> Json_error e) 234 + 235 + let to_string t = 236 + match Jsont_bytesrw.encode_string jsont t with 237 + | Ok s -> s 238 + | Error e -> failwith ("JSON encoding error: " ^ e) 239 + 240 + let pp ppf t = 241 + Format.fprintf ppf "@[<v>"; 242 + Option.iter (Format.fprintf ppf "subject: %s@,") t.subject; 243 + if t.aliases <> [] then begin 244 + Format.fprintf ppf "aliases:@,"; 245 + List.iter (Format.fprintf ppf " - %s@,") t.aliases 246 + end; 247 + if t.properties <> [] then begin 248 + Format.fprintf ppf "properties:@,"; 249 + pp_properties ppf t.properties 250 + end; 251 + if t.links <> [] then begin 252 + Format.fprintf ppf "links:@,"; 253 + List.iter (fun link -> Format.fprintf ppf " %a@," Link.pp link) t.links 254 + end; 255 + Format.fprintf ppf "@]" 256 + end 257 + 258 + (** {1 Common Link Relations} *) 259 + 260 + module Rel = struct 261 + let activitypub = "self" 262 + let openid = "http://openid.net/specs/connect/1.0/issuer" 263 + let profile = "http://webfinger.net/rel/profile-page" 264 + let avatar = "http://webfinger.net/rel/avatar" 265 + let feed = "http://schemas.google.com/g/2010#updates-from" 266 + let portable_contacts = "http://portablecontacts.net/spec/1.0" 267 + let oauth_authorization = "http://tools.ietf.org/html/rfc6749#section-3.1" 268 + let oauth_token = "http://tools.ietf.org/html/rfc6749#section-3.2" 269 + let subscribe = "http://ostatus.org/schema/1.0/subscribe" 270 + let salmon = "salmon" 271 + let magic_key = "magic-public-key" 272 + end 273 + 274 + (** {1 URL Construction} *) 275 + 276 + let webfinger_url ~resource ?(rels = []) host = 277 + let base = Printf.sprintf "https://%s/.well-known/webfinger" host in 278 + let uri = Uri.of_string base in 279 + let uri = Uri.add_query_param' uri ("resource", resource) in 280 + let uri = List.fold_left (fun u rel -> Uri.add_query_param' u ("rel", rel)) uri rels in 281 + Uri.to_string uri 282 + 283 + let webfinger_url_acct acct ?(rels = []) () = 284 + let resource = Acct.to_string acct in 285 + let host = Acct.host acct in 286 + webfinger_url ~resource ~rels host 287 + 288 + let host_of_resource resource = 289 + if String.starts_with ~prefix:"acct:" resource then 290 + Acct.of_string resource |> Result.map Acct.host 291 + else 292 + let uri = Uri.of_string resource in 293 + match Uri.host uri with 294 + | Some host -> Ok host 295 + | None -> Error (Invalid_resource "Cannot determine host from resource URI") 296 + 297 + (** {1 HTTP Client} *) 298 + 299 + let query session ~resource ?(rels = []) () = 300 + match host_of_resource resource with 301 + | Error e -> Error e 302 + | Ok host -> 303 + let url = webfinger_url ~resource ~rels host in 304 + Log.info (fun m -> m "WebFinger query: %s" url); 305 + let headers = Requests.Headers.empty |> Requests.Headers.set `Accept "application/jrd+json" in 306 + let response = Requests.get session ~headers url in 307 + let status = Requests.Response.status_code response in 308 + let body = Eio.Flow.read_all (Requests.Response.body response) in 309 + if status = 404 then Error Not_found 310 + else if status >= 400 then Error (Http_error { status; body }) 311 + else Jrd.of_string body 312 + 313 + let query_exn session ~resource ?rels () = 314 + match query session ~resource ?rels () with 315 + | Ok jrd -> jrd 316 + | Error e -> raise_error e 317 + 318 + let query_acct session acct ?(rels = []) () = 319 + let resource = Acct.to_string acct in 320 + let host = Acct.host acct in 321 + let url = webfinger_url ~resource ~rels host in 322 + Log.info (fun m -> m "WebFinger query: %s" url); 323 + let headers = Requests.Headers.empty |> Requests.Headers.set `Accept "application/jrd+json" in 324 + let response = Requests.get session ~headers url in 325 + let status = Requests.Response.status_code response in 326 + let body = Eio.Flow.read_all (Requests.Response.body response) in 327 + if status = 404 then Error Not_found 328 + else if status >= 400 then Error (Http_error { status; body }) 329 + else Jrd.of_string body 330 + 331 + let query_acct_exn session acct ?rels () = 332 + match query_acct session acct ?rels () with 333 + | Ok jrd -> jrd 334 + | Error e -> raise_error e
+303
lib/webfinger.mli
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** RFC 7033 WebFinger and RFC 7565 acct URI scheme. 7 + 8 + This module implements the WebFinger protocol as specified in 9 + {{:https://datatracker.ietf.org/doc/html/rfc7033}RFC 7033}, providing 10 + type-safe JSON Resource Descriptor (JRD) encoding/decoding and an 11 + HTTP client for WebFinger queries. It also implements the acct URI 12 + scheme as specified in {{:https://datatracker.ietf.org/doc/html/rfc7565}RFC 7565}. 13 + 14 + {2 Example} 15 + {[ 16 + Eio_main.run @@ fun env -> 17 + Eio.Switch.run @@ fun sw -> 18 + let session = Requests.create ~sw env in 19 + let acct = Webfinger.Acct.of_string_exn "acct:user@example.com" in 20 + match Webfinger.query_acct session acct () with 21 + | Ok jrd -> 22 + Format.printf "%a@." Webfinger.Jrd.pp jrd; 23 + begin match Webfinger.Jrd.find_link ~rel:"self" jrd with 24 + | Some link -> Format.printf "ActivityPub: %s@." (Option.get (Webfinger.Link.href link)) 25 + | None -> () 26 + end 27 + | Error e -> 28 + Format.eprintf "Error: %a@." Webfinger.pp_error e 29 + ]} 30 + 31 + {2 References} 32 + {ul 33 + {- {{:https://datatracker.ietf.org/doc/html/rfc7033}RFC 7033} - WebFinger} 34 + {- {{:https://datatracker.ietf.org/doc/html/rfc7565}RFC 7565} - The 'acct' URI Scheme} 35 + {- {{:https://datatracker.ietf.org/doc/html/rfc6415}RFC 6415} - Web Host Metadata}} *) 36 + 37 + (** {1 Error Types} *) 38 + 39 + type error = 40 + | Invalid_resource of string 41 + (** The resource parameter is missing or malformed. *) 42 + | Http_error of { status : int; body : string } 43 + (** HTTP request failed with the given status code. *) 44 + | Json_error of string 45 + (** Failed to parse JRD JSON response. *) 46 + | Https_required 47 + (** WebFinger requires HTTPS but HTTP was requested. *) 48 + | Not_found 49 + (** The server has no information about the requested resource. *) 50 + 51 + val pp_error : Format.formatter -> error -> unit 52 + (** [pp_error fmt e] pretty-prints an error. *) 53 + 54 + val error_to_string : error -> string 55 + (** [error_to_string e] converts an error to a human-readable string. *) 56 + 57 + exception Webfinger_error of error 58 + (** Exception raised by [*_exn] functions. *) 59 + 60 + (** {1 Acct URI} 61 + 62 + The acct URI scheme as specified in 63 + {{:https://datatracker.ietf.org/doc/html/rfc7565}RFC 7565}. *) 64 + 65 + module Acct : sig 66 + type t 67 + (** An acct URI identifying a user account at a service provider. 68 + 69 + Per {{:https://datatracker.ietf.org/doc/html/rfc7565#section-4}RFC 7565 Section 4}, 70 + an acct URI is used for identification only, not interaction. *) 71 + 72 + val make : userpart:string -> host:string -> t 73 + (** [make ~userpart ~host] creates an acct URI. 74 + 75 + The [userpart] should be the raw (unencoded) account name. Any characters 76 + requiring percent-encoding (such as [@] in email addresses) will be 77 + automatically encoded by {!to_string}. 78 + 79 + @raise Invalid_argument if [userpart] or [host] is empty. *) 80 + 81 + val of_string : string -> (t, error) result 82 + (** [of_string s] parses an acct URI string. 83 + 84 + Accepts URIs of the form ["acct:userpart\@host"]. The userpart may contain 85 + percent-encoded characters which are decoded. *) 86 + 87 + val of_string_exn : string -> t 88 + (** [of_string_exn s] is like {!of_string} but raises {!Webfinger_error}. *) 89 + 90 + val to_string : t -> string 91 + (** [to_string acct] serializes an acct URI to string form. 92 + 93 + Characters in the userpart that require encoding per RFC 7565 are 94 + percent-encoded. *) 95 + 96 + val userpart : t -> string 97 + (** [userpart acct] returns the decoded userpart (account name). *) 98 + 99 + val host : t -> string 100 + (** [host acct] returns the host (service provider domain). *) 101 + 102 + val equal : t -> t -> bool 103 + (** [equal a b] compares two acct URIs using case normalization and 104 + percent-encoding normalization as specified in 105 + {{:https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2}RFC 3986 Section 6.2.2}. *) 106 + 107 + val pp : Format.formatter -> t -> unit 108 + (** [pp fmt acct] pretty-prints an acct URI. *) 109 + end 110 + 111 + (** {1 Link} 112 + 113 + Link relation object as specified in 114 + {{:https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4}RFC 7033 Section 4.4.4}. *) 115 + 116 + module Link : sig 117 + type t 118 + (** A link relation in a JRD. *) 119 + 120 + val make : 121 + rel:string -> 122 + ?type_:string -> 123 + ?href:string -> 124 + ?titles:(string * string) list -> 125 + ?properties:(string * string option) list -> 126 + unit -> t 127 + (** [make ~rel ?type_ ?href ?titles ?properties ()] creates a link. *) 128 + 129 + val rel : t -> string 130 + (** [rel link] returns the link relation type. *) 131 + 132 + val type_ : t -> string option 133 + (** [type_ link] returns the media type. *) 134 + 135 + val href : t -> string option 136 + (** [href link] returns the target URI. *) 137 + 138 + val titles : t -> (string * string) list 139 + (** [titles link] returns all title/language pairs. *) 140 + 141 + val properties : t -> (string * string option) list 142 + (** [properties link] returns all link properties. *) 143 + 144 + val title : ?lang:string -> t -> string option 145 + (** [title ?lang link] returns the title for [lang] (default "und"). *) 146 + 147 + val property : uri:string -> t -> string option 148 + (** [property ~uri link] returns the property value for [uri]. *) 149 + 150 + val jsont : t Jsont.t 151 + (** JSON type descriptor for links. *) 152 + 153 + val pp : Format.formatter -> t -> unit 154 + (** [pp fmt link] pretty-prints a link. *) 155 + end 156 + 157 + (** {1 JRD} 158 + 159 + JSON Resource Descriptor as specified in 160 + {{:https://datatracker.ietf.org/doc/html/rfc7033#section-4.4}RFC 7033 Section 4.4}. *) 161 + 162 + module Jrd : sig 163 + type t 164 + (** A JSON Resource Descriptor. *) 165 + 166 + val make : 167 + ?subject:string -> 168 + ?aliases:string list -> 169 + ?properties:(string * string option) list -> 170 + ?links:Link.t list -> 171 + unit -> t 172 + (** [make ?subject ?aliases ?properties ?links ()] creates a JRD. *) 173 + 174 + val subject : t -> string option 175 + (** [subject jrd] returns the subject URI. *) 176 + 177 + val aliases : t -> string list 178 + (** [aliases jrd] returns the list of alias URIs. *) 179 + 180 + val properties : t -> (string * string option) list 181 + (** [properties jrd] returns subject properties. *) 182 + 183 + val links : t -> Link.t list 184 + (** [links jrd] returns all links. *) 185 + 186 + val find_link : rel:string -> t -> Link.t option 187 + (** [find_link ~rel jrd] returns the first link with relation [rel]. *) 188 + 189 + val find_links : rel:string -> t -> Link.t list 190 + (** [find_links ~rel jrd] returns all links with relation [rel]. *) 191 + 192 + val property : uri:string -> t -> string option 193 + (** [property ~uri jrd] returns the property value for [uri]. *) 194 + 195 + val jsont : t Jsont.t 196 + (** JSON type descriptor for JRD. *) 197 + 198 + val of_string : string -> (t, error) result 199 + (** [of_string s] parses a JRD from JSON. *) 200 + 201 + val to_string : t -> string 202 + (** [to_string jrd] serializes a JRD to JSON. *) 203 + 204 + val pp : Format.formatter -> t -> unit 205 + (** [pp fmt jrd] pretty-prints a JRD. *) 206 + end 207 + 208 + (** {1 Common Link Relations} *) 209 + 210 + module Rel : sig 211 + val activitypub : string 212 + (** ["self"] - ActivityPub actor profile. *) 213 + 214 + val openid : string 215 + (** OpenID Connect issuer. *) 216 + 217 + val profile : string 218 + (** Profile page. *) 219 + 220 + val avatar : string 221 + (** Avatar image. *) 222 + 223 + val feed : string 224 + (** Atom/RSS feed. *) 225 + 226 + val portable_contacts : string 227 + (** Portable Contacts. *) 228 + 229 + val oauth_authorization : string 230 + (** OAuth 2.0 authorization endpoint. *) 231 + 232 + val oauth_token : string 233 + (** OAuth 2.0 token endpoint. *) 234 + 235 + val subscribe : string 236 + (** Subscribe to resource (OStatus). *) 237 + 238 + val salmon : string 239 + (** Salmon endpoint (legacy). *) 240 + 241 + val magic_key : string 242 + (** Magic public key (legacy). *) 243 + end 244 + 245 + (** {1 URL Construction} *) 246 + 247 + val webfinger_url : resource:string -> ?rels:string list -> string -> string 248 + (** [webfinger_url ~resource ?rels host] constructs the WebFinger query URL. 249 + 250 + The [resource] is the URI to query (typically an acct: or https: URI). 251 + Optional [rels] filters the response to only include matching link relations. *) 252 + 253 + val webfinger_url_acct : Acct.t -> ?rels:string list -> unit -> string 254 + (** [webfinger_url_acct acct ?rels ()] constructs the WebFinger query URL for an acct URI. 255 + 256 + The host is automatically extracted from the acct URI. *) 257 + 258 + val host_of_resource : string -> (string, error) result 259 + (** [host_of_resource resource] extracts the host from an acct: or https: URI. 260 + 261 + For acct: URIs, returns the host portion after the [@]. 262 + For https: URIs, returns the host from the URI. *) 263 + 264 + (** {1 HTTP Client} *) 265 + 266 + val query : 267 + Requests.t -> 268 + resource:string -> 269 + ?rels:string list -> 270 + unit -> (Jrd.t, error) result 271 + (** [query session ~resource ?rels ()] performs a WebFinger query. 272 + 273 + Per {{:https://datatracker.ietf.org/doc/html/rfc7033#section-4}RFC 7033 Section 4}: 274 + - Queries use HTTPS 275 + - The Accept header requests application/jrd+json 276 + - 200 OK returns a JRD 277 + - 404 means no information available *) 278 + 279 + val query_exn : 280 + Requests.t -> 281 + resource:string -> 282 + ?rels:string list -> 283 + unit -> Jrd.t 284 + (** [query_exn session ~resource ?rels ()] is like {!query} but raises 285 + {!Webfinger_error} on failure. *) 286 + 287 + val query_acct : 288 + Requests.t -> 289 + Acct.t -> 290 + ?rels:string list -> 291 + unit -> (Jrd.t, error) result 292 + (** [query_acct session acct ?rels ()] performs a WebFinger query for an acct URI. 293 + 294 + This is the preferred way to query for user accounts as it ensures 295 + the resource is a valid acct URI per RFC 7565. *) 296 + 297 + val query_acct_exn : 298 + Requests.t -> 299 + Acct.t -> 300 + ?rels:string list -> 301 + unit -> Jrd.t 302 + (** [query_acct_exn session acct ?rels ()] is like {!query_acct} but raises 303 + {!Webfinger_error} on failure. *)
+1571
spec/rfc7033.txt
··· 1 + 2 + 3 + 4 + 5 + 6 + 7 + Internet Engineering Task Force (IETF) P. Jones 8 + Request for Comments: 7033 G. Salgueiro 9 + Category: Standards Track Cisco Systems 10 + ISSN: 2070-1721 M. Jones 11 + Microsoft 12 + J. Smarr 13 + Google 14 + September 2013 15 + 16 + 17 + WebFinger 18 + 19 + Abstract 20 + 21 + This specification defines the WebFinger protocol, which can be used 22 + to discover information about people or other entities on the 23 + Internet using standard HTTP methods. WebFinger discovers 24 + information for a URI that might not be usable as a locator 25 + otherwise, such as account or email URIs. 26 + 27 + Status of This Memo 28 + 29 + This is an Internet Standards Track document. 30 + 31 + This document is a product of the Internet Engineering Task Force 32 + (IETF). It represents the consensus of the IETF community. It has 33 + received public review and has been approved for publication by the 34 + Internet Engineering Steering Group (IESG). Further information on 35 + Internet Standards is available in Section 2 of RFC 5741. 36 + 37 + Information about the current status of this document, any errata, 38 + and how to provide feedback on it may be obtained at 39 + http://www.rfc-editor.org/info/rfc7033. 40 + 41 + Copyright Notice 42 + 43 + Copyright (c) 2013 IETF Trust and the persons identified as the 44 + document authors. All rights reserved. 45 + 46 + This document is subject to BCP 78 and the IETF Trust's Legal 47 + Provisions Relating to IETF Documents 48 + (http://trustee.ietf.org/license-info) in effect on the date of 49 + publication of this document. Please review these documents 50 + carefully, as they describe your rights and restrictions with respect 51 + to this document. Code Components extracted from this document must 52 + include Simplified BSD License text as described in Section 4.e of 53 + the Trust Legal Provisions and are provided without warranty as 54 + described in the Simplified BSD License. 55 + 56 + 57 + 58 + Jones, et al. Standards Track [Page 1] 59 + 60 + RFC 7033 WebFinger September 2013 61 + 62 + 63 + Table of Contents 64 + 65 + 1. Introduction ....................................................3 66 + 2. Terminology .....................................................3 67 + 3. Example Uses of WebFinger .......................................4 68 + 3.1. Identity Provider Discovery for OpenID Connect .............4 69 + 3.2. Getting Author and Copyright Information for a Web Page ....5 70 + 4. WebFinger Protocol ..............................................7 71 + 4.1. Constructing the Query Component of the Request URI.......7 72 + 4.2. Performing a WebFinger Query..............................8 73 + 4.3. The "rel" Parameter.......................................9 74 + 4.4. The JSON Resource Descriptor (JRD).......................11 75 + 4.4.1. subject.............................................11 76 + 4.4.2. aliases.............................................11 77 + 4.4.3. properties..........................................12 78 + 4.4.4. links...............................................12 79 + 4.5. WebFinger and URIs.......................................14 80 + 5. Cross-Origin Resource Sharing (CORS) ...........................14 81 + 6. Access Control .................................................15 82 + 7. Hosted WebFinger Services ......................................15 83 + 8. Definition of WebFinger Applications ...........................16 84 + 8.1. Specification of the URI Scheme and URI ...................17 85 + 8.2. Host Resolution ...........................................17 86 + 8.3. Specification of Properties ...............................17 87 + 8.4. Specification of Links ....................................18 88 + 8.5. One URI, Multiple Applications ............................18 89 + 8.6. Registration of Link Relation Types and Properties ........19 90 + 9. Security Considerations ........................................19 91 + 9.1. Transport-Related Issues ..................................19 92 + 9.2. User Privacy Considerations ...............................19 93 + 9.3. Abuse Potential ...........................................21 94 + 9.4. Information Reliability ...................................21 95 + 10. IANA Considerations ...........................................22 96 + 10.1. Well-Known URI ...........................................22 97 + 10.2. JSON Resource Descriptor (JRD) Media Type ................22 98 + 10.3. Registering Link Relation Types ..........................24 99 + 10.4. Establishment of the "WebFinger Properties" Registry .....24 100 + 10.4.1. The Registration Template .........................24 101 + 10.4.2. The Registration Procedures .......................25 102 + 11. Acknowledgments ...............................................26 103 + 12. References ....................................................26 104 + 12.1. Normative References .....................................26 105 + 12.2. Informative References ...................................27 106 + 107 + 108 + 109 + 110 + 111 + 112 + 113 + 114 + Jones, et al. Standards Track [Page 2] 115 + 116 + RFC 7033 WebFinger September 2013 117 + 118 + 119 + 1. Introduction 120 + 121 + WebFinger is used to discover information about people or other 122 + entities on the Internet that are identified by a URI [6] using 123 + standard Hypertext Transfer Protocol (HTTP) [2] methods over a secure 124 + transport [12]. A WebFinger resource returns a JavaScript Object 125 + Notation (JSON) [5] object describing the entity that is queried. 126 + The JSON object is referred to as the JSON Resource Descriptor (JRD). 127 + 128 + For a person, the type of information that might be discoverable via 129 + WebFinger includes a personal profile address, identity service, 130 + telephone number, or preferred avatar. For other entities on the 131 + Internet, a WebFinger resource might return JRDs containing link 132 + relations [8] that enable a client to discover, for example, that a 133 + printer can print in color on A4 paper, the physical location of a 134 + server, or other static information. 135 + 136 + Information returned via WebFinger might be for direct human 137 + consumption (e.g., looking up someone's phone number), or it might be 138 + used by systems to help carry out some operation (e.g., facilitating, 139 + with additional security mechanisms, logging into a web site by 140 + determining a user's identity service). The information is intended 141 + to be static in nature, and, as such, WebFinger is not intended to be 142 + used to return dynamic information like the temperature of a CPU or 143 + the current toner level in a laser printer. 144 + 145 + The WebFinger protocol is designed to be used across many 146 + applications. Applications that wish to utilize WebFinger will need 147 + to specify properties, titles, and link relation types that are 148 + appropriate for the application. Further, applications will need to 149 + define the appropriate URI scheme to utilize for the query target. 150 + 151 + Use of WebFinger is illustrated in the examples in Section 3 and 152 + described more formally in Section 4. Section 8 describes how 153 + applications of WebFinger may be defined. 154 + 155 + 2. Terminology 156 + 157 + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 158 + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 159 + document are to be interpreted as described in RFC 2119 [1]. 160 + 161 + WebFinger makes heavy use of "link relations". A link relation is an 162 + attribute-value pair in which the attribute identifies the type of 163 + relationship between the linked entity or resource and the 164 + information specified in the value. In Web Linking [4], the link 165 + relation is represented using an HTTP entity-header of "Link", where 166 + the "rel" attribute specifies the type of relationship and the "href" 167 + 168 + 169 + 170 + Jones, et al. Standards Track [Page 3] 171 + 172 + RFC 7033 WebFinger September 2013 173 + 174 + 175 + attribute specifies the information that is linked to the entity or 176 + resource. In WebFinger, the same concept is represented using a JSON 177 + array of "links" objects, where each member named "rel" specifies the 178 + type of relationship and each member named "href" specifies the 179 + information that is linked to the entity or resource. Note that 180 + WebFinger narrows the scope of a link relation beyond what is defined 181 + for Web Linking by stipulating that the value of the "rel" member 182 + needs to be either a single IANA-registered link relation type [8] or 183 + a URI [6]. 184 + 185 + The use of URIs throughout this document refers to URIs following the 186 + syntax specified in Section 3 of RFC 3986 [6]. Relative URIs, having 187 + syntax following that of Section 4.2 of RFC 3986, are not used with 188 + WebFinger. 189 + 190 + 3. Example Uses of WebFinger 191 + 192 + This section shows a few sample uses of WebFinger. Any application 193 + of WebFinger would be specified outside of this document, as 194 + described in Section 8. The examples in this section should be 195 + simple enough to understand without having seen the formal 196 + specifications of the applications. 197 + 198 + 3.1. Identity Provider Discovery for OpenID Connect 199 + 200 + Suppose Carol wishes to authenticate with a web site she visits using 201 + OpenID Connect [15]. She would provide the web site with her OpenID 202 + Connect identifier, say carol@example.com. The visited web site 203 + would perform a WebFinger query looking for the OpenID Connect 204 + provider. Since the site is interested in only one particular link 205 + relation, the WebFinger resource might utilize the "rel" parameter as 206 + described in Section 4.3: 207 + 208 + GET /.well-known/webfinger? 209 + resource=acct%3Acarol%40example.com& 210 + rel=http%3A%2F%2Fopenid.net%2Fspecs%2Fconnect%2F1.0%2Fissuer 211 + HTTP/1.1 212 + Host: example.com 213 + 214 + 215 + 216 + 217 + 218 + 219 + 220 + 221 + 222 + 223 + 224 + 225 + 226 + Jones, et al. Standards Track [Page 4] 227 + 228 + RFC 7033 WebFinger September 2013 229 + 230 + 231 + The server might respond like this: 232 + 233 + HTTP/1.1 200 OK 234 + Access-Control-Allow-Origin: * 235 + Content-Type: application/jrd+json 236 + 237 + { 238 + "subject" : "acct:carol@example.com", 239 + "links" : 240 + [ 241 + { 242 + "rel" : "http://openid.net/specs/connect/1.0/issuer", 243 + "href" : "https://openid.example.com" 244 + } 245 + ] 246 + } 247 + 248 + Since the "rel" parameter only serves to filter the link relations 249 + returned by the resource, other name/value pairs in the response, 250 + including any aliases or properties, would be returned. Also, since 251 + support for the "rel" parameter is not guaranteed, the client must 252 + not assume the "links" array will contain only the requested link 253 + relation. 254 + 255 + 3.2. Getting Author and Copyright Information for a Web Page 256 + 257 + Suppose an application is defined to retrieve metadata information 258 + about a web page URL, such as author and copyright information. To 259 + retrieve that information, the client can utilize WebFinger to issue 260 + a query for the specific URL. Suppose the URL of interest is 261 + http://blog.example.com/article/id/314. The client would issue a 262 + query similar to the following: 263 + 264 + GET /.well-known/webfinger? 265 + resource=http%3A%2F%2Fblog.example.com%2Farticle%2Fid%2F314 266 + HTTP/1.1 267 + Host: blog.example.com 268 + 269 + 270 + 271 + 272 + 273 + 274 + 275 + 276 + 277 + 278 + 279 + 280 + 281 + 282 + Jones, et al. Standards Track [Page 5] 283 + 284 + RFC 7033 WebFinger September 2013 285 + 286 + 287 + The server might then reply in this way: 288 + 289 + HTTP/1.1 200 OK 290 + Access-Control-Allow-Origin: * 291 + Content-Type: application/jrd+json 292 + 293 + { 294 + "subject" : "http://blog.example.com/article/id/314", 295 + "aliases" : 296 + [ 297 + "http://blog.example.com/cool_new_thing", 298 + "http://blog.example.com/steve/article/7" 299 + ], 300 + "properties" : 301 + { 302 + "http://blgx.example.net/ns/version" : "1.3", 303 + "http://blgx.example.net/ns/ext" : null 304 + }, 305 + "links" : 306 + [ 307 + { 308 + "rel" : "copyright", 309 + "href" : "http://www.example.com/copyright" 310 + }, 311 + { 312 + "rel" : "author", 313 + "href" : "http://blog.example.com/author/steve", 314 + "titles" : 315 + { 316 + "en-us" : "The Magical World of Steve", 317 + "fr" : "Le Monde Magique de Steve" 318 + }, 319 + "properties" : 320 + { 321 + "http://example.com/role" : "editor" 322 + } 323 + } 324 + 325 + ] 326 + } 327 + 328 + In the above example, we see that the server returned a list of 329 + aliases, properties, and links related to the subject URL. The links 330 + contain references to information for each link relation type. For 331 + the author link, the server provided a reference to the author's 332 + blog, along with a title for the blog in two languages. The server 333 + also returned a single property related to the author, indicating the 334 + author's role as editor of the blog. 335 + 336 + 337 + 338 + Jones, et al. Standards Track [Page 6] 339 + 340 + RFC 7033 WebFinger September 2013 341 + 342 + 343 + It is worth noting that, while the server returned just two links in 344 + the "links" array in this example, a server might return any number 345 + of links when queried. 346 + 347 + 4. WebFinger Protocol 348 + 349 + The WebFinger protocol is used to request information about an entity 350 + identified by a query target (a URI). The client can optionally 351 + specify one or more link relation types for which it would like to 352 + receive information. 353 + 354 + A WebFinger request is an HTTPS request to a WebFinger resource. A 355 + WebFinger resource is a well-known URI [3] using the HTTPS scheme 356 + constructed along with the required query target and optional link 357 + relation types. WebFinger resources MUST NOT be served with any 358 + other URI scheme (such as HTTP). 359 + 360 + A WebFinger resource is always given a query target, which is another 361 + URI that identifies the entity whose information is sought. GET 362 + requests to a WebFinger resource convey the query target in the 363 + "resource" parameter of the WebFinger URI's query string; see Section 364 + 4.1 for details. 365 + 366 + The host to which a WebFinger query is issued is significant. If the 367 + query target contains a "host" portion (Section 3.2.2 of RFC 3986), 368 + then the host to which the WebFinger query is issued SHOULD be the 369 + same as the "host" portion of the query target, unless the client 370 + receives instructions through some out-of-band mechanism to send the 371 + query to another host. If the query target does not contain a "host" 372 + portion, then the client chooses a host to which it directs the query 373 + using additional information it has. 374 + 375 + The path component of a WebFinger URI MUST be the well-known path 376 + "/.well-known/webfinger". A WebFinger URI MUST contain a query 377 + component that encodes the query target and optional link relation 378 + types as specified in Section 4.1. 379 + 380 + The WebFinger resource returns a JSON Resource Descriptor (JRD) as 381 + the resource representation to convey information about an entity on 382 + the Internet. Also, the Cross-Origin Resource Sharing (CORS) [7] 383 + specification is utilized to facilitate queries made via a web 384 + browser. 385 + 386 + 4.1. Constructing the Query Component of the Request URI 387 + 388 + A WebFinger URI MUST contain a query component (see Section 3.4 of 389 + RFC 3986). The query component MUST contain a "resource" parameter 390 + and MAY contain one or more "rel" parameters. The "resource" 391 + 392 + 393 + 394 + Jones, et al. Standards Track [Page 7] 395 + 396 + RFC 7033 WebFinger September 2013 397 + 398 + 399 + parameter MUST contain the query target (URI), and the "rel" 400 + parameters MUST contain encoded link relation types according to the 401 + encoding described in this section. 402 + 403 + To construct the query component, the client performs the following 404 + steps. First, each parameter value is percent-encoded, as per 405 + Section 2.1 of RFC 3986. The encoding is done to conform to the 406 + query production in Section 3.4 of that specification, with the 407 + addition that any instances of the "=" and "&" characters within the 408 + parameter values are also percent-encoded. Next, the client 409 + constructs a string to be placed in the query component by 410 + concatenating the name of the first parameter together with an equal 411 + sign ("=") and the percent-encoded parameter value. For any 412 + subsequent parameters, the client appends an ampersand ("&") to the 413 + string, the name of the next parameter, an equal sign, and the 414 + parameter value. The client MUST NOT insert any spaces while 415 + constructing the string. The order in which the client places each 416 + attribute-value pair within the query component does not matter in 417 + the interpretation of the query component. 418 + 419 + 4.2. Performing a WebFinger Query 420 + 421 + A WebFinger client issues a query using the GET method to the well- 422 + known [3] resource identified by the URI whose path component is 423 + "/.well-known/webfinger" and whose query component MUST include the 424 + "resource" parameter exactly once and set to the value of the URI for 425 + which information is being sought. 426 + 427 + If the "resource" parameter is absent or malformed, the WebFinger 428 + resource MUST indicate that the request is bad as per Section 10.4.1 429 + of RFC 2616 [2]. 430 + 431 + If the "resource" parameter is a value for which the server has no 432 + information, the server MUST indicate that it was unable to match the 433 + request as per Section 10.4.5 of RFC 2616. 434 + 435 + A client MUST query the WebFinger resource using HTTPS only. If the 436 + client determines that the resource has an invalid certificate, the 437 + resource returns a 4xx or 5xx status code, or if the HTTPS connection 438 + cannot be established for any reason, then the client MUST accept 439 + that the WebFinger query has failed and MUST NOT attempt to reissue 440 + the WebFinger request using HTTP over a non-secure connection. 441 + 442 + A WebFinger resource MUST return a JRD as the representation for the 443 + resource if the client requests no other supported format explicitly 444 + via the HTTP "Accept" header. The client MAY include the "Accept" 445 + header to indicate a desired representation; representations other 446 + than JRD might be defined in future specifications. The WebFinger 447 + 448 + 449 + 450 + Jones, et al. Standards Track [Page 8] 451 + 452 + RFC 7033 WebFinger September 2013 453 + 454 + 455 + resource MUST silently ignore any requested representations that it 456 + does not understand or support. The media type used for the JSON 457 + Resource Descriptor (JRD) is "application/jrd+json" (see Section 458 + 10.2). 459 + 460 + The properties, titles, and link relation types returned by the 461 + server in a JRD might be varied and numerous. For example, the 462 + server might return information about a person's blog, vCard [14], 463 + avatar, OpenID Connect provider, RSS or ATOM feed, and so forth in a 464 + reply. Likewise, if a server has no information to provide, it might 465 + return a JRD with an empty "links" array or no "links" array. 466 + 467 + A WebFinger resource MAY redirect the client; if it does, the 468 + redirection MUST only be to an "https" URI and the client MUST 469 + perform certificate validation again when redirected. 470 + 471 + A WebFinger resource can include cache validators in a response to 472 + enable conditional requests by the client and/or expiration times as 473 + per Section 13 of RFC 2616. 474 + 475 + 4.3. The "rel" Parameter 476 + 477 + When issuing a request to a WebFinger resource, the client MAY 478 + utilize the "rel" parameter to request only a subset of the 479 + information that would otherwise be returned without the "rel" 480 + parameter. When the "rel" parameter is used and accepted, only the 481 + link relation types that match the link relation type provided via 482 + the "rel" parameter are included in the array of links returned in 483 + the JRD. If there are no matching link relation types defined for 484 + the resource, the "links" array in the JRD will be either absent or 485 + empty. All other information present in a resource descriptor 486 + remains present, even when "rel" is employed. 487 + 488 + The "rel" parameter MAY be included multiple times in order to 489 + request multiple link relation types. 490 + 491 + The purpose of the "rel" parameter is to return a subset of "link 492 + relation objects" (see Section 4.4.4) that would otherwise be 493 + returned in the resource descriptor. Use of the parameter might 494 + reduce processing requirements on either the client or server, and it 495 + might also reduce the bandwidth required to convey the partial 496 + resource descriptor, especially if there are numerous link relation 497 + values to convey for a given "resource" value. Note that if a client 498 + requests a particular link relation type for which the server has no 499 + information, the server MAY return a JRD with an empty "links" array 500 + or no "links" array. 501 + 502 + 503 + 504 + 505 + 506 + Jones, et al. Standards Track [Page 9] 507 + 508 + RFC 7033 WebFinger September 2013 509 + 510 + 511 + WebFinger resources SHOULD support the "rel" parameter. If the 512 + resource does not support the "rel" parameter, it MUST ignore the 513 + parameter and process the request as if no "rel" parameter values 514 + were present. 515 + 516 + The following example uses the "rel" parameter to request links for 517 + two link relation types: 518 + 519 + GET /.well-known/webfinger? 520 + resource=acct%3Abob%40example.com& 521 + rel=http%3A%2F%2Fwebfinger.example%2Frel%2Fprofile-page& 522 + rel=http%3A%2F%2Fwebfinger.example%2Frel%2Fbusinesscard HTTP/1.1 523 + Host: example.com 524 + 525 + In this example, the client requests the link relations of type 526 + "http://webfinger.example/rel/profile-page" and 527 + "http://webfinger.example/rel/businesscard". The server then 528 + responds with a message like this: 529 + 530 + HTTP/1.1 200 OK 531 + Access-Control-Allow-Origin: * 532 + Content-Type: application/jrd+json 533 + 534 + { 535 + "subject" : "acct:bob@example.com", 536 + "aliases" : 537 + [ 538 + "https://www.example.com/~bob/" 539 + ], 540 + "properties" : 541 + { 542 + "http://example.com/ns/role" : "employee" 543 + }, 544 + "links" : 545 + [ 546 + { 547 + "rel" : "http://webfinger.example/rel/profile-page", 548 + "href" : "https://www.example.com/~bob/" 549 + }, 550 + { 551 + "rel" : "http://webfinger.example/rel/businesscard", 552 + "href" : "https://www.example.com/~bob/bob.vcf" 553 + } 554 + ] 555 + } 556 + 557 + 558 + 559 + 560 + 561 + 562 + Jones, et al. Standards Track [Page 10] 563 + 564 + RFC 7033 WebFinger September 2013 565 + 566 + 567 + As you can see in the response, the resource representation contains 568 + only the links of the types requested by the client and for which the 569 + server had information, but the other parts of the JRD are still 570 + present. Note also in the above example that the links returned in 571 + the "links" array all use HTTPS, which is important if the data 572 + indirectly obtained via WebFinger needs to be returned securely. 573 + 574 + 4.4. The JSON Resource Descriptor (JRD) 575 + 576 + The JSON Resource Descriptor (JRD), originally introduced in RFC 6415 577 + [16] and based on the Extensible Resource Descriptor (XRD) format 578 + [17], is a JSON object that comprises the following name/value pairs: 579 + 580 + o subject 581 + o aliases 582 + o properties 583 + o links 584 + 585 + The member "subject" is a name/value pair whose value is a string, 586 + "aliases" is an array of strings, "properties" is an object 587 + comprising name/value pairs whose values are strings, and "links" is 588 + an array of objects that contain link relation information. 589 + 590 + When processing a JRD, the client MUST ignore any unknown member and 591 + not treat the presence of an unknown member as an error. 592 + 593 + Below, each of these members of the JRD is described in more detail. 594 + 595 + 4.4.1. subject 596 + 597 + The value of the "subject" member is a URI that identifies the entity 598 + that the JRD describes. 599 + 600 + The "subject" value returned by a WebFinger resource MAY differ from 601 + the value of the "resource" parameter used in the client's request. 602 + This might happen, for example, when the subject's identity changes 603 + (e.g., a user moves his or her account to another service) or when 604 + the resource prefers to express URIs in canonical form. 605 + 606 + The "subject" member SHOULD be present in the JRD. 607 + 608 + 4.4.2. aliases 609 + 610 + The "aliases" array is an array of zero or more URI strings that 611 + identify the same entity as the "subject" URI. 612 + 613 + The "aliases" array is OPTIONAL in the JRD. 614 + 615 + 616 + 617 + 618 + Jones, et al. Standards Track [Page 11] 619 + 620 + RFC 7033 WebFinger September 2013 621 + 622 + 623 + 4.4.3. properties 624 + 625 + The "properties" object comprises zero or more name/value pairs whose 626 + names are URIs (referred to as "property identifiers") and whose 627 + values are strings or null. Properties are used to convey additional 628 + information about the subject of the JRD. As an example, consider 629 + this use of "properties": 630 + 631 + "properties" : { "http://webfinger.example/ns/name" : "Bob Smith" } 632 + 633 + The "properties" member is OPTIONAL in the JRD. 634 + 635 + 4.4.4. links 636 + 637 + The "links" array has any number of member objects, each of which 638 + represents a link [4]. Each of these link objects can have the 639 + following members: 640 + 641 + o rel 642 + o type 643 + o href 644 + o titles 645 + o properties 646 + 647 + The "rel" and "href" members are strings representing the link's 648 + relation type and the target URI, respectively. The context of the 649 + link is the "subject" (see Section 4.4.1). 650 + 651 + The "type" member is a string indicating what the media type of the 652 + result of dereferencing the link ought to be. 653 + 654 + The order of elements in the "links" array MAY be interpreted as 655 + indicating an order of preference. Thus, if there are two or more 656 + link relations having the same "rel" value, the first link relation 657 + would indicate the user's preferred link. 658 + 659 + The "links" array is OPTIONAL in the JRD. 660 + 661 + Below, each of the members of the objects found in the "links" array 662 + is described in more detail. Each object in the "links" array, 663 + referred to as a "link relation object", is completely independent 664 + from any other object in the array; any requirement to include a 665 + given member in the link relation object refers only to that 666 + particular object. 667 + 668 + 669 + 670 + 671 + 672 + 673 + 674 + Jones, et al. Standards Track [Page 12] 675 + 676 + RFC 7033 WebFinger September 2013 677 + 678 + 679 + 4.4.4.1. rel 680 + 681 + The value of the "rel" member is a string that is either a URI or a 682 + registered relation type [8] (see RFC 5988 [4]). The value of the 683 + "rel" member MUST contain exactly one URI or registered relation 684 + type. The URI or registered relation type identifies the type of the 685 + link relation. 686 + 687 + The other members of the object have meaning only once the type of 688 + link relation is understood. In some instances, the link relation 689 + will have associated semantics enabling the client to query for other 690 + resources on the Internet. In other instances, the link relation 691 + will have associated semantics enabling the client to utilize the 692 + other members of the link relation object without fetching additional 693 + external resources. 694 + 695 + URI link relation type values are compared using the "Simple String 696 + Comparison" algorithm of Section 6.2.1 of RFC 3986. 697 + 698 + The "rel" member MUST be present in the link relation object. 699 + 700 + 4.4.4.2. type 701 + 702 + The value of the "type" member is a string that indicates the media 703 + type [9] of the target resource (see RFC 6838 [10]). 704 + 705 + The "type" member is OPTIONAL in the link relation object. 706 + 707 + 4.4.4.3. href 708 + 709 + The value of the "href" member is a string that contains a URI 710 + pointing to the target resource. 711 + 712 + The "href" member is OPTIONAL in the link relation object. 713 + 714 + 4.4.4.4. titles 715 + 716 + The "titles" object comprises zero or more name/value pairs whose 717 + names are a language tag [11] or the string "und". The string is 718 + human-readable and describes the link relation. More than one title 719 + for the link relation MAY be provided for the benefit of users who 720 + utilize the link relation, and, if used, a language identifier SHOULD 721 + be duly used as the name. If the language is unknown or unspecified, 722 + then the name is "und". 723 + 724 + A JRD SHOULD NOT include more than one title identified with the same 725 + language tag (or "und") within the link relation object. Meaning is 726 + undefined if a link relation object includes more than one title 727 + 728 + 729 + 730 + Jones, et al. Standards Track [Page 13] 731 + 732 + RFC 7033 WebFinger September 2013 733 + 734 + 735 + named with the same language tag (or "und"), though this MUST NOT be 736 + treated as an error. A client MAY select whichever title or titles 737 + it wishes to utilize. 738 + 739 + Here is an example of the "titles" object: 740 + 741 + "titles" : 742 + { 743 + "en-us" : "The Magical World of Steve", 744 + "fr" : "Le Monde Magique de Steve" 745 + } 746 + 747 + The "titles" member is OPTIONAL in the link relation object. 748 + 749 + 4.4.4.5. properties 750 + 751 + The "properties" object within the link relation object comprises 752 + zero or more name/value pairs whose names are URIs (referred to as 753 + "property identifiers") and whose values are strings or null. 754 + Properties are used to convey additional information about the link 755 + relation. As an example, consider this use of "properties": 756 + 757 + "properties" : { "http://webfinger.example/mail/port" : "993" } 758 + 759 + The "properties" member is OPTIONAL in the link relation object. 760 + 761 + 4.5. WebFinger and URIs 762 + 763 + WebFinger requests include a "resource" parameter (see Section 4.1) 764 + specifying the query target (URI) for which the client requests 765 + information. WebFinger is neutral regarding the scheme of such a 766 + URI: it could be an "acct" URI [18], an "http" or "https" URI, a 767 + "mailto" URI [19], or some other scheme. 768 + 769 + 5. Cross-Origin Resource Sharing (CORS) 770 + 771 + WebFinger resources might not be accessible from a web browser due to 772 + "Same-Origin" policies. The current best practice is to make 773 + resources available to browsers through Cross-Origin Resource Sharing 774 + (CORS) [7], and servers MUST include the Access-Control-Allow-Origin 775 + HTTP header in responses. Servers SHOULD support the least 776 + restrictive setting by allowing any domain access to the WebFinger 777 + resource: 778 + 779 + Access-Control-Allow-Origin: * 780 + 781 + 782 + 783 + 784 + 785 + 786 + Jones, et al. Standards Track [Page 14] 787 + 788 + RFC 7033 WebFinger September 2013 789 + 790 + 791 + There are cases where defaulting to the least restrictive setting is 792 + not appropriate. For example, a server on an intranet that provides 793 + sensitive company information SHOULD NOT allow CORS requests from any 794 + domain, as that could allow leaking of that sensitive information. A 795 + server that wishes to restrict access to information from external 796 + entities SHOULD use a more restrictive Access-Control-Allow-Origin 797 + header. 798 + 799 + 6. Access Control 800 + 801 + As with all web resources, access to the WebFinger resource could 802 + require authentication. Further, failure to provide required 803 + credentials might result in the server forbidding access or providing 804 + a different response than had the client authenticated with the 805 + server. 806 + 807 + Likewise, a WebFinger resource MAY provide different responses to 808 + different clients based on other factors, such as whether the client 809 + is inside or outside a corporate network. As a concrete example, a 810 + query performed on the internal corporate network might return link 811 + relations to employee pictures, whereas link relations for employee 812 + pictures might not be provided to external entities. 813 + 814 + Further, link relations provided in a WebFinger resource 815 + representation might point to web resources that impose access 816 + restrictions. For example, the aforementioned corporate server may 817 + provide both internal and external entities with URIs to employee 818 + pictures, but further authentication might be required in order for 819 + the client to access the picture resources if the request comes from 820 + outside the corporate network. 821 + 822 + The decisions made with respect to what set of link relations a 823 + WebFinger resource provides to one client versus another and what 824 + resources require further authentication, as well as the specific 825 + authentication mechanisms employed, are outside the scope of this 826 + document. 827 + 828 + 7. Hosted WebFinger Services 829 + 830 + As with most services provided on the Internet, it is possible for a 831 + domain owner to utilize "hosted" WebFinger services. By way of 832 + example, a domain owner might control most aspects of their domain 833 + but use a third-party hosting service for email. In the case of 834 + email, mail exchange (MX) records identify mail servers for a domain. 835 + An MX record points to the mail server to which mail for the domain 836 + should be delivered. To the sending server, it does not matter 837 + whether those MX records point to a server in the destination domain 838 + or a different domain. 839 + 840 + 841 + 842 + Jones, et al. Standards Track [Page 15] 843 + 844 + RFC 7033 WebFinger September 2013 845 + 846 + 847 + Likewise, a domain owner might utilize the services of a third party 848 + to provide WebFinger services on behalf of its users. Just as a 849 + domain owner is required to insert MX records into DNS to allow for 850 + hosted email services, the domain owner is required to redirect HTTP 851 + queries to its domain to allow for hosted WebFinger services. 852 + 853 + When a query is issued to the WebFinger resource, the web server MUST 854 + return a response with a redirection status code that includes a 855 + Location header pointing to the location of the hosted WebFinger 856 + service URI. This WebFinger service URI does not need to point to 857 + the well-known WebFinger location on the hosting service provider 858 + server. 859 + 860 + As an example, assume that example.com's WebFinger services are 861 + hosted by wf.example.net. Suppose a client issues a query for 862 + acct:alice@example.com like this: 863 + 864 + GET /.well-known/webfinger? 865 + resource=acct%3Aalice%40example.com HTTP/1.1 866 + Host: example.com 867 + 868 + The server might respond with this: 869 + 870 + HTTP/1.1 307 Temporary Redirect 871 + Access-Control-Allow-Origin: * 872 + Location: https://wf.example.net/example.com/webfinger? 873 + resource=acct%3Aalice%40example.com 874 + 875 + The client can then follow the redirection, reissuing the request to 876 + the URI provided in the Location header. Note that the server will 877 + include any required URI parameters in the Location header value, 878 + which could be different than the URI parameters the client 879 + originally used. 880 + 881 + 8. Definition of WebFinger Applications 882 + 883 + This specification details the protocol syntax used to query a domain 884 + for information about a URI, the syntax of the JSON Resource 885 + Descriptor (JRD) that is returned in response to that query, security 886 + requirements and considerations, hosted WebFinger services, various 887 + expected HTTP status codes, and so forth. However, this 888 + specification does not enumerate the various possible properties or 889 + link relation types that might be used in conjunction with WebFinger 890 + for a particular application, nor does it define what properties or 891 + link relation types one might expect to see in response to querying 892 + for a particular URI or URI scheme. Nonetheless, all of these 893 + unspecified elements are important in order to implement an 894 + interoperable application that utilizes the WebFinger protocol and 895 + 896 + 897 + 898 + Jones, et al. Standards Track [Page 16] 899 + 900 + RFC 7033 WebFinger September 2013 901 + 902 + 903 + MUST be specified in the relevant document(s) defining the particular 904 + application making use of the WebFinger protocol according to the 905 + procedures described in this section. 906 + 907 + 8.1. Specification of the URI Scheme and URI 908 + 909 + Any application that uses WebFinger MUST specify the URI scheme(s), 910 + and to the extent appropriate, what forms the URI(s) might take. For 911 + example, when querying for information about a user's account at some 912 + domain, it might make sense to specify the use of the "acct" URI 913 + scheme [18]. When trying to obtain the copyright information for a 914 + web page, it makes sense to specify the use of the web page URI 915 + (either http or https). 916 + 917 + The examples in Sections 3.1 and 3.2 illustrate the use of different 918 + URI schemes with WebFinger applications. In the example in Section 919 + 3.1, WebFinger is used to retrieve information pertinent to OpenID 920 + Connect. In the example in Section 3.2, WebFinger is used to 921 + discover metadata information about a web page, including author and 922 + copyright information. Each of these WebFinger applications needs to 923 + be fully specified to ensure interoperability. 924 + 925 + 8.2. Host Resolution 926 + 927 + As explained in Section 4, the host to which a WebFinger query is 928 + issued is significant. In general, WebFinger applications would 929 + adhere to the procedures described in Section 4 in order to properly 930 + direct a WebFinger query. 931 + 932 + However, some URI schemes do not have host portions and there might 933 + be some applications of WebFinger for which the host portion of a URI 934 + cannot or should not be utilized. In such instances, the application 935 + specification MUST clearly define the host resolution procedures, 936 + which might include provisioning a "default" host within the client 937 + to which queries are directed. 938 + 939 + 8.3. Specification of Properties 940 + 941 + WebFinger defines both subject-specific properties (i.e., properties 942 + described in Section 4.4.3 that relate to the URI for which 943 + information is queried) and link-specific properties (see Section 944 + 4.4.4.5). This section refers to subject-specific properties. 945 + 946 + Applications that utilize subject-specific properties MUST define the 947 + URIs used in identifying those properties, along with valid property 948 + values. 949 + 950 + 951 + 952 + 953 + 954 + Jones, et al. Standards Track [Page 17] 955 + 956 + RFC 7033 WebFinger September 2013 957 + 958 + 959 + Consider this portion of the JRD found in the example in Section 3.2. 960 + 961 + "properties" : 962 + { 963 + "http://blgx.example.net/ns/version" : "1.3", 964 + "http://blgx.example.net/ns/ext" : null 965 + } 966 + 967 + Here, two properties are returned in the WebFinger response. Each of 968 + these would be defined in a WebFinger application specification. 969 + These two properties might be defined in the same WebFinger 970 + application specification or separately in different specifications. 971 + Since the latter is possible, it is important that WebFinger clients 972 + not assume that one property has any specific relationship with 973 + another property, unless some relationship is explicitly defined in 974 + the particular WebFinger application specification. 975 + 976 + 8.4. Specification of Links 977 + 978 + The links returned in a WebFinger response each comprise several 979 + pieces of information, some of which are optional (refer to Section 980 + 4.4.4). The WebFinger application specification MUST define each 981 + link and any values associated with a link, including the link 982 + relation type ("rel"), the expected media type ("type"), properties, 983 + and titles. 984 + 985 + The target URI to which the link refers (i.e., the "href"), if 986 + present, would not normally be specified in an application 987 + specification. However, the URI scheme or any special 988 + characteristics of the URI would usually be specified. If a 989 + particular link does not require an external reference, then all of 990 + the semantics related to the use of that link MUST be defined within 991 + the application specification. Such links might rely only on 992 + properties or titles in the link to convey meaning. 993 + 994 + 8.5. One URI, Multiple Applications 995 + 996 + It is important to be mindful of the fact that different WebFinger 997 + applications might specify the use of the same URI scheme, and in 998 + effect, the same URI for different purposes. That should not be a 999 + problem, since each of property identifier (see Sections 4.4.3 and 1000 + 4.4.4.5) and link relation type would be uniquely defined for a 1001 + specific application. 1002 + 1003 + It should be noted that when a client requests information about a 1004 + particular URI and receives a response with a number of different 1005 + property identifiers or link relation types that the response is 1006 + providing information about the URI without any particular semantics. 1007 + 1008 + 1009 + 1010 + Jones, et al. Standards Track [Page 18] 1011 + 1012 + RFC 7033 WebFinger September 2013 1013 + 1014 + 1015 + How the client interprets the information SHOULD be in accordance 1016 + with the particular application specification or set of 1017 + specifications the client implements. 1018 + 1019 + Any syntactically valid properties or links the client receives and 1020 + that are not fully understood SHOULD be ignored and SHOULD NOT cause 1021 + the client to report an error. 1022 + 1023 + 8.6. Registration of Link Relation Types and Properties 1024 + 1025 + Application specifications MAY define a simple token as a link 1026 + relation type for a link. In that case, the link relation type MUST 1027 + be registered with IANA as specified in Sections 10.3. 1028 + 1029 + Further, any defined properties MUST be registered with IANA as 1030 + described in Section 10.4. 1031 + 1032 + 9. Security Considerations 1033 + 1034 + 9.1. Transport-Related Issues 1035 + 1036 + Since this specification utilizes Cross-Origin Resource Sharing 1037 + (CORS) [7], all of the security considerations applicable to CORS are 1038 + also applicable to this specification. 1039 + 1040 + The use of HTTPS is REQUIRED to ensure that information is not 1041 + modified during transit. It should be acknowledged that in 1042 + environments where a web server is normally available, there exists 1043 + the possibility that a compromised network might have its WebFinger 1044 + resource operating on HTTPS replaced with one operating only over 1045 + HTTP. As such, clients MUST NOT issue queries over a non-secure 1046 + connection. 1047 + 1048 + Clients MUST verify that the certificate used on an HTTPS connection 1049 + is valid (as defined in [12]) and accept a response only if the 1050 + certificate is valid. 1051 + 1052 + 9.2. User Privacy Considerations 1053 + 1054 + Service providers and users should be aware that placing information 1055 + on the Internet means that any user can access that information, and 1056 + WebFinger can be used to make it even easier to discover that 1057 + information. While WebFinger can be an extremely useful tool for 1058 + discovering one's avatar, blog, or other personal data, users should 1059 + also understand the risks. 1060 + 1061 + 1062 + 1063 + 1064 + 1065 + 1066 + Jones, et al. Standards Track [Page 19] 1067 + 1068 + RFC 7033 WebFinger September 2013 1069 + 1070 + 1071 + Systems or services that expose personal data via WebFinger MUST 1072 + provide an interface by which users can select which data elements 1073 + are exposed through the WebFinger interface. For example, social 1074 + networking sites might allow users to mark certain data as "public" 1075 + and then utilize that marking as a means of determining what 1076 + information to expose via WebFinger. The information published via 1077 + WebFinger would thus comprise only the information marked as public 1078 + by the user. Further, the user has the ability to remove information 1079 + from publication via WebFinger by removing this marking. 1080 + 1081 + WebFinger MUST NOT be used to provide any personal data unless 1082 + publishing that data via WebFinger by the relevant service was 1083 + explicitly authorized by the person whose information is being 1084 + shared. Publishing one's personal data within an access-controlled 1085 + or otherwise limited environment on the Internet does not equate to 1086 + providing implicit authorization of further publication of that data 1087 + via WebFinger. 1088 + 1089 + The privacy and security concerns with publishing personal data via 1090 + WebFinger are worth emphasizing again with respect to personal data 1091 + that might reveal a user's current context (e.g., the user's 1092 + location). The power of WebFinger comes from providing a single 1093 + place where others can find pointers to information about a person, 1094 + but service providers and users should be mindful of the nature of 1095 + that information shared and the fact that it might be available for 1096 + the entire world to see. Sharing location information, for example, 1097 + would potentially put a person in danger from any individual who 1098 + might seek to inflict harm on that person. 1099 + 1100 + Users should be aware of how easily personal data that one might 1101 + publish can be used in unintended ways. In one study relevant to 1102 + WebFinger-like services, Balduzzi et al. [20] took a large set of 1103 + leaked email addresses and demonstrated a number of potential privacy 1104 + concerns, including the ability to cross-correlate the same user's 1105 + accounts over multiple social networks. The authors also describe 1106 + potential mitigation strategies. 1107 + 1108 + The easy access to user information via WebFinger was a design goal 1109 + of the protocol, not a limitation. If one wishes to limit access to 1110 + information available via WebFinger, such as WebFinger resources for 1111 + use inside a corporate network, the network administrator needs to 1112 + take necessary measures to limit access from outside the network. 1113 + Using standard methods for securing web resources, network 1114 + administrators do have the ability to control access to resources 1115 + that might return sensitive information. Further, a server can be 1116 + employed in such a way as to require authentication and prevent 1117 + disclosure of information to unauthorized entities. 1118 + 1119 + 1120 + 1121 + 1122 + Jones, et al. Standards Track [Page 20] 1123 + 1124 + RFC 7033 WebFinger September 2013 1125 + 1126 + 1127 + 9.3. Abuse Potential 1128 + 1129 + Service providers should be mindful of the potential for abuse using 1130 + WebFinger. 1131 + 1132 + As one example, one might query a WebFinger server only to discover 1133 + whether or not a given URI is valid. With such a query, the person 1134 + may deduce that an email identifier is valid, for example. Such an 1135 + approach could help spammers maintain a current list of known email 1136 + addresses and to discover new ones. 1137 + 1138 + WebFinger could be used to associate a name or other personal data 1139 + with an email address, allowing spammers to craft more convincing 1140 + email messages. This might be of particular value in phishing 1141 + attempts. 1142 + 1143 + It is RECOMMENDED that implementers of WebFinger server software take 1144 + steps to mitigate abuse, including malicious over-use of the server 1145 + and harvesting of user information. Although there is no mechanism 1146 + that can guarantee that publicly accessible WebFinger databases won't 1147 + be harvested, rate-limiting by IP address will prevent or at least 1148 + dramatically slow harvest by private individuals without access to 1149 + botnets or other distributed systems. The reason these mitigation 1150 + strategies are not mandatory is that the correct choice of mitigation 1151 + strategy (if any) depends greatly on the context. Implementers 1152 + should not construe this as meaning that they do not need to consider 1153 + whether to use a mitigation strategy, and if so, what strategy to 1154 + use. 1155 + 1156 + WebFinger client developers should also be aware of potential abuse 1157 + by spammers or those phishing for information about users. As an 1158 + example, suppose a mail client was configured to automatically 1159 + perform a WebFinger query on the sender of each received mail 1160 + message. If a spammer sent an email using a unique identifier in the 1161 + 'From' header, then when the WebFinger query was performed, the 1162 + spammer would be able to associate the request with a particular 1163 + user's email address. This would provide information to the spammer, 1164 + including the user's IP address, the fact the user just checked 1165 + email, what kind of WebFinger client the user utilized, and so on. 1166 + For this reason, it is strongly advised that clients not perform 1167 + WebFinger queries unless authorized by the user to do so. 1168 + 1169 + 9.4. Information Reliability 1170 + 1171 + A WebFinger resource has no means of ensuring that information 1172 + provided by a user is accurate. Likewise, neither the resource nor 1173 + the client can be absolutely guaranteed that information has not been 1174 + manipulated either at the server or along the communication path 1175 + 1176 + 1177 + 1178 + Jones, et al. Standards Track [Page 21] 1179 + 1180 + RFC 7033 WebFinger September 2013 1181 + 1182 + 1183 + between the client and server. Use of HTTPS helps to address some 1184 + concerns with manipulation of information along the communication 1185 + path, but it clearly cannot address issues where the resource 1186 + provided incorrect information, either due to being provided false 1187 + information or due to malicious behavior on the part of the server 1188 + administrator. As with any information service available on the 1189 + Internet, users should be wary of information received from untrusted 1190 + sources. 1191 + 1192 + 10. IANA Considerations 1193 + 1194 + 10.1. Well-Known URI 1195 + 1196 + This specification registers the "webfinger" well-known URI in the 1197 + "Well-Known URIs" registry as defined by RFC 5785 [3]. 1198 + 1199 + URI suffix: webfinger 1200 + 1201 + Change controller: IETF 1202 + 1203 + Specification document(s): RFC 7033 1204 + 1205 + Related information: The query to the WebFinger resource will 1206 + include one or more parameters in the query string; see Section 4.1 1207 + of RFC 7033. Resources at this location are able to return a JSON 1208 + Resource Descriptor (JRD) as described in Section 4.4 of RFC 7033. 1209 + 1210 + 10.2. JSON Resource Descriptor (JRD) Media Type 1211 + 1212 + This specification registers the media type application/jrd+json for 1213 + use with WebFinger in accordance with media type registration 1214 + procedures defined in RFC 6838 [10]. 1215 + 1216 + Type name: application 1217 + 1218 + Subtype name: jrd+json 1219 + 1220 + Required parameters: N/A 1221 + 1222 + Optional parameters: N/A 1223 + 1224 + In particular, because RFC 4627 already defines the character 1225 + encoding for JSON, no "charset" parameter is used. 1226 + 1227 + Encoding considerations: See RFC 6839, Section 3.1. 1228 + 1229 + 1230 + 1231 + 1232 + 1233 + 1234 + Jones, et al. Standards Track [Page 22] 1235 + 1236 + RFC 7033 WebFinger September 2013 1237 + 1238 + 1239 + Security considerations: 1240 + 1241 + The JSON Resource Descriptor (JRD) is a JavaScript Object Notation 1242 + (JSON) object. It is a text format that must be parsed by entities 1243 + that wish to utilize the format. Depending on the language and 1244 + mechanism used to parse a JSON object, it is possible for an 1245 + attacker to inject behavior into a running program. Therefore, 1246 + care must be taken to properly parse a received JRD to ensure that 1247 + only a valid JSON object is present and that no JavaScript or other 1248 + code is injected or executed unexpectedly. 1249 + 1250 + Interoperability considerations: 1251 + 1252 + This media type is a JavaScript Object Notation (JSON) object and 1253 + can be consumed by any software application that can consume JSON 1254 + objects. 1255 + 1256 + Published specification: RFC 7033 1257 + 1258 + Applications that use this media type: 1259 + 1260 + The JSON Resource Descriptor (JRD) is used by the WebFinger 1261 + protocol (RFC 7033) to enable the exchange of information between a 1262 + client and a WebFinger resource over HTTPS. 1263 + 1264 + Fragment identifier considerations: 1265 + 1266 + The syntax and semantics of fragment identifiers SHOULD be as 1267 + specified for "application/json". (At publication of this 1268 + document, there is no fragment identification syntax defined for 1269 + "application/json".) 1270 + 1271 + Additional information: 1272 + 1273 + Deprecated alias names for this type: N/A 1274 + 1275 + Magic number(s): N/A 1276 + 1277 + File extension(s): jrd 1278 + 1279 + Macintosh file type code(s): N/A 1280 + 1281 + Person & email address to contact for further information: 1282 + 1283 + Paul E. Jones <paulej@packetizer.com> 1284 + 1285 + Intended usage: COMMON 1286 + 1287 + 1288 + 1289 + 1290 + Jones, et al. Standards Track [Page 23] 1291 + 1292 + RFC 7033 WebFinger September 2013 1293 + 1294 + 1295 + Restrictions on usage: N/A 1296 + 1297 + Author: Paul E. Jones <paulej@packetizer.com> 1298 + 1299 + Change controller: 1300 + 1301 + IESG has change control over this registration. 1302 + 1303 + Provisional registration? (standards tree only): N/A 1304 + 1305 + 10.3. Registering Link Relation Types 1306 + 1307 + RFC 5988 established a "Link Relation Types" registry that is reused 1308 + by WebFinger applications. 1309 + 1310 + Link relation types used by WebFinger applications are registered in 1311 + the "Link Relation Types" registry as per the procedures of Section 1312 + 6.2.1 of RFC 5988. The "Notes" entry for the registration SHOULD 1313 + indicate if property values associated with the link relation type 1314 + are registered in the "WebFinger Properties" registry with a link to 1315 + the registry. 1316 + 1317 + 10.4. Establishment of the "WebFinger Properties" Registry 1318 + 1319 + WebFinger utilizes URIs to identify properties of a subject or link 1320 + and the associated values (see Sections 8.3 and 8.6). This 1321 + specification establishes a new "WebFinger Properties" registry to 1322 + record property identifiers. 1323 + 1324 + 10.4.1. The Registration Template 1325 + 1326 + The registration template for WebFinger properties is: 1327 + 1328 + o Property Identifier: 1329 + 1330 + o Link Type: 1331 + 1332 + o Description: 1333 + 1334 + o Reference: 1335 + 1336 + o Notes: [optional] 1337 + 1338 + The "Property Identifier" must be a URI that identifies the property 1339 + being registered. 1340 + 1341 + 1342 + 1343 + 1344 + 1345 + 1346 + Jones, et al. Standards Track [Page 24] 1347 + 1348 + RFC 7033 WebFinger September 2013 1349 + 1350 + 1351 + The "Link Type" contains the name of a link relation type with which 1352 + this property identifier is used. If the property is a subject- 1353 + specific property, then this field is specified as "N/A". 1354 + 1355 + The "Description" is intended to explain the purpose of the property. 1356 + 1357 + The "Reference" field points to the specification that defines the 1358 + registered property. 1359 + 1360 + The optional "Notes" field is for conveying any useful information 1361 + about the property that might be of value to implementers. 1362 + 1363 + 10.4.2. The Registration Procedures 1364 + 1365 + The IETF has created a mailing list, webfinger@ietf.org, which can be 1366 + used for public discussion of the WebFinger protocol and any 1367 + applications that use it. Prior to registration of a WebFinger 1368 + property, discussion on the mailing list is strongly encouraged. The 1369 + IESG has appointed Designated Experts [13] who will monitor the 1370 + webfinger@ietf.org mailing list and review registrations. 1371 + 1372 + A WebFinger property is registered with a Specification Required (see 1373 + RFC 5226 [13]) after a review by the Designated Experts. The review 1374 + is normally expected to take on the order of two to four weeks. 1375 + However, the Designated Experts may approve a registration prior to 1376 + publication of a specification once the Designated Experts are 1377 + satisfied that such a specification will be published. In evaluating 1378 + registration requests, the Designated Experts should make an effort 1379 + to avoid registering two different properties that have the same 1380 + meaning. Where a proposed property is similar to an already-defined 1381 + property, the Designated Experts should insist that enough text be 1382 + included in the description or notes section of the template to 1383 + sufficiently differentiate the new property from an existing one. 1384 + 1385 + The registration procedure begins with a completed registration 1386 + template (as defined above) sent to webfinger@ietf.org. Once 1387 + consensus is reached on the mailing list, the registration template 1388 + is sent to iana@iana.org. IANA will then contact the Designated 1389 + Experts and communicate the results to the registrant. The WebFinger 1390 + mailing list provides an opportunity for community discussion and 1391 + input, and the Designated Experts may use that input to inform their 1392 + review. Denials should include an explanation and, if applicable, 1393 + suggestions as to how to make the request successful if resubmitted. 1394 + 1395 + 1396 + 1397 + 1398 + 1399 + 1400 + 1401 + 1402 + Jones, et al. Standards Track [Page 25] 1403 + 1404 + RFC 7033 WebFinger September 2013 1405 + 1406 + 1407 + The specification registering the WebFinger property MUST include the 1408 + completed registration template shown above. Once the registration 1409 + procedure concludes successfully, IANA creates or modifies the 1410 + corresponding record in the "WebFinger Properties" registry. 1411 + 1412 + 11. Acknowledgments 1413 + 1414 + This document has benefited from extensive discussion and review by 1415 + many of the members of the APPSAWG working group. The authors would 1416 + like to especially acknowledge the invaluable input of Eran Hammer- 1417 + Lahav, Blaine Cook, Brad Fitzpatrick, Laurent-Walter Goix, Joe 1418 + Clarke, Peter Saint-Andre, Dick Hardt, Tim Bray, James Snell, Melvin 1419 + Carvalho, Evan Prodromou, Mark Nottingham, Elf Pavlik, Bjoern 1420 + Hoehrmann, Subramanian Moonesamy, Joe Gregorio, John Bradley, and 1421 + others that we have undoubtedly, but inadvertently, missed. 1422 + 1423 + The authors would also like to express their gratitude to the chairs 1424 + of the APPSAWG working group, especially Salvatore Loreto for his 1425 + assistance in shepherding this document. We also want to thank Barry 1426 + Leiba and Pete Resnick, the Applications Area Directors, for their 1427 + support and exhaustive reviews. 1428 + 1429 + 12. References 1430 + 1431 + 12.1. Normative References 1432 + 1433 + [1] Bradner, S., "Key words for use in RFCs to Indicate Requirement 1434 + Levels", BCP 14, RFC 2119, March 1997. 1435 + 1436 + [2] Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., 1437 + Leach, P., and T. Berners-Lee, "Hypertext Transfer Protocol 1438 + -- HTTP/1.1", RFC 2616, June 1999. 1439 + 1440 + [3] Nottingham, M. and E. Hammer-Lahav, "Defining Well-Known 1441 + Uniform Resource Identifiers (URIs)", RFC 5785, April 2010. 1442 + 1443 + [4] Nottingham, M., "Web Linking", RFC 5988, October 2010. 1444 + 1445 + [5] Crockford, D., "The application/json Media Type for JavaScript 1446 + Object Notation (JSON)", RFC 4627, July 2006. 1447 + 1448 + [6] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform 1449 + Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, 1450 + January 2005. 1451 + 1452 + [7] Van Kesteren, A., "Cross-Origin Resource Sharing", W3C CORS, 1453 + July 2010, <http://www.w3.org/TR/cors/>. 1454 + 1455 + 1456 + 1457 + 1458 + Jones, et al. Standards Track [Page 26] 1459 + 1460 + RFC 7033 WebFinger September 2013 1461 + 1462 + 1463 + [8] IANA, "Link Relations", 1464 + <http://www.iana.org/assignments/link-relations/>. 1465 + 1466 + [9] IANA, "MIME Media Types", 1467 + <http://www.iana.org/assignments/media-types>. 1468 + 1469 + [10] Freed, N., Klensin, J., and T. Hansen, "Media Type 1470 + Specifications and Registration Procedures", BCP 13, RFC 6838, 1471 + January 2013. 1472 + 1473 + [11] Phillips, A., Ed., and M. Davis, Ed., "Tags for Identifying 1474 + Languages", BCP 47, RFC 5646, September 2009. 1475 + 1476 + [12] Rescorla, E., "HTTP Over TLS", RFC 2818, May 2000. 1477 + 1478 + [13] Narten, T. and H. Alvestrand, "Guidelines for Writing an IANA 1479 + Considerations Section in RFCs", BCP 26, RFC 5226, May 2008. 1480 + 1481 + 12.2. Informative References 1482 + 1483 + [14] Perreault, S., "vCard Format Specification", RFC 6350, August 1484 + 2011. 1485 + 1486 + [15] Sakimura, N., Bradley, J., Jones, M., de Medeiros, B., 1487 + Mortimore, C., and E. Jay, "OpenID Connect Messages 1.0", 1488 + July 2013, 1489 + <http://openid.net/specs/openid-connect-messages-1_0.html>. 1490 + 1491 + [16] Hammer-Lahav, E., Ed., and B. Cook, "Web Host Metadata", RFC 1492 + 6415, October 2011. 1493 + 1494 + [17] Hammer-Lahav, E. and W. Norris, "Extensible Resource Descriptor 1495 + (XRD) Version 1.0", 1496 + <http://docs.oasis-open.org/xri/xrd/v1.0/xrd-1.0.html>. 1497 + 1498 + [18] Saint-Andre, P., "The 'acct' URI Scheme", Work in Progress, 1499 + July 2013. 1500 + 1501 + [19] Duerst, M., Masinter, L., and J. Zawinski, "The 'mailto' URI 1502 + Scheme", RFC 6068, October 2010. 1503 + 1504 + [20] Balduzzi, M., Platzer, C., Thorsten, H., Kirda, E., Balzarotti, 1505 + D., and C. Kruegel "Abusing Social Networks for Automated User 1506 + Profiling", Recent Advances in Intrusion Detection, Springer 1507 + Berlin Heidelberg, March 2010, 1508 + <https://www.eurecom.fr/en/publication/3042/download/ 1509 + rs-publi-3042_1.pdf>. 1510 + 1511 + 1512 + 1513 + 1514 + Jones, et al. Standards Track [Page 27] 1515 + 1516 + RFC 7033 WebFinger September 2013 1517 + 1518 + 1519 + Authors' Addresses 1520 + 1521 + Paul E. Jones 1522 + Cisco Systems, Inc. 1523 + 7025 Kit Creek Rd. 1524 + Research Triangle Park, NC 27709 1525 + USA 1526 + 1527 + Phone: +1 919 476 2048 1528 + EMail: paulej@packetizer.com 1529 + IM: xmpp:paulej@packetizer.com 1530 + 1531 + 1532 + Gonzalo Salgueiro 1533 + Cisco Systems, Inc. 1534 + 7025 Kit Creek Rd. 1535 + Research Triangle Park, NC 27709 1536 + USA 1537 + 1538 + Phone: +1 919 392 3266 1539 + EMail: gsalguei@cisco.com 1540 + IM: xmpp:gsalguei@cisco.com 1541 + 1542 + 1543 + Michael B. Jones 1544 + Microsoft 1545 + 1546 + EMail: mbj@microsoft.com 1547 + URI: http://self-issued.info/ 1548 + 1549 + 1550 + Joseph Smarr 1551 + Google 1552 + 1553 + EMail: jsmarr@google.com 1554 + URI: http://josephsmarr.com/ 1555 + 1556 + 1557 + 1558 + 1559 + 1560 + 1561 + 1562 + 1563 + 1564 + 1565 + 1566 + 1567 + 1568 + 1569 + 1570 + Jones, et al. Standards Track [Page 28] 1571 +
+451
spec/rfc7565.txt
··· 1 + 2 + 3 + 4 + 5 + 6 + 7 + Internet Engineering Task Force (IETF) P. Saint-Andre 8 + Request for Comments: 7565 May 2015 9 + Category: Standards Track 10 + ISSN: 2070-1721 11 + 12 + 13 + The 'acct' URI Scheme 14 + 15 + Abstract 16 + 17 + This document defines the 'acct' Uniform Resource Identifier (URI) 18 + scheme as a way to identify a user's account at a service provider, 19 + irrespective of the particular protocols that can be used to interact 20 + with the account. 21 + 22 + Status of This Memo 23 + 24 + This is an Internet Standards Track document. 25 + 26 + This document is a product of the Internet Engineering Task Force 27 + (IETF). It represents the consensus of the IETF community. It has 28 + received public review and has been approved for publication by the 29 + Internet Engineering Steering Group (IESG). Further information on 30 + Internet Standards is available in Section 2 of RFC 5741. 31 + 32 + Information about the current status of this document, any errata, 33 + and how to provide feedback on it may be obtained at 34 + http://www.rfc-editor.org/info/rfc7565. 35 + 36 + Copyright Notice 37 + 38 + Copyright (c) 2015 IETF Trust and the persons identified as the 39 + document authors. All rights reserved. 40 + 41 + This document is subject to BCP 78 and the IETF Trust's Legal 42 + Provisions Relating to IETF Documents 43 + (http://trustee.ietf.org/license-info) in effect on the date of 44 + publication of this document. Please review these documents 45 + carefully, as they describe your rights and restrictions with respect 46 + to this document. Code Components extracted from this document must 47 + include Simplified BSD License text as described in Section 4.e of 48 + the Trust Legal Provisions and are provided without warranty as 49 + described in the Simplified BSD License. 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + Saint-Andre Standards Track [Page 1] 59 + 60 + RFC 7565 The 'acct' URI Scheme May 2015 61 + 62 + 63 + Table of Contents 64 + 65 + 1. Introduction ....................................................2 66 + 2. Terminology .....................................................2 67 + 3. Rationale .......................................................2 68 + 4. Definition ......................................................3 69 + 5. Security Considerations .........................................4 70 + 6. Internationalization Considerations .............................5 71 + 7. IANA Considerations .............................................5 72 + 8. References ......................................................6 73 + 8.1. Normative References .......................................6 74 + 8.2. Informative References .....................................7 75 + Acknowledgements ...................................................8 76 + Author's Address ...................................................8 77 + 78 + 1. Introduction 79 + 80 + Existing Uniform Resource Identifier (URI) schemes that enable 81 + interaction with, or that identify resources associated with, a 82 + user's account at a service provider are tied to particular services 83 + or application protocols. Two examples are the 'mailto' scheme 84 + (which enables interaction with a user's email account) and the 85 + 'http' scheme (which enables retrieval of web files controlled by a 86 + user or interaction with interfaces providing information about a 87 + user). However, there exists no URI scheme that generically 88 + identifies a user's account at a service provider without specifying 89 + a particular protocol to use when interacting with the account. This 90 + specification fills that gap. 91 + 92 + 2. Terminology 93 + 94 + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 95 + "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and 96 + "OPTIONAL" in this document are to be interpreted as described in 97 + [RFC2119]. 98 + 99 + 3. Rationale 100 + 101 + During formalization of the WebFinger protocol [RFC7033], much 102 + discussion occurred regarding the appropriate URI scheme to include 103 + when specifying a user's account as a web link [RFC5988]. Although 104 + both the 'mailto' [RFC6068] and 'http' [RFC7230] schemes were 105 + proposed, not all service providers offer email services or web 106 + interfaces on behalf of user accounts (e.g., a microblogging or 107 + instant messaging provider might not offer email services, or an 108 + enterprise might not offer HTTP interfaces to information about its 109 + employees). Therefore, the participants in the discussion recognized 110 + that it would be helpful to define a URI scheme that could be used to 111 + 112 + 113 + 114 + Saint-Andre Standards Track [Page 2] 115 + 116 + RFC 7565 The 'acct' URI Scheme May 2015 117 + 118 + 119 + generically identify a user's account at a service provider, 120 + irrespective of the particular application protocols used to interact 121 + with the account. The result was the 'acct' URI scheme defined in 122 + this document. 123 + 124 + (Note that a user is not necessarily a human; it could be an 125 + automated application such as a bot, a role-based alias, etc. 126 + However, an 'acct' URI is always used to identify something that has 127 + an account at a service, not the service itself.) 128 + 129 + 4. Definition 130 + 131 + The syntax of the 'acct' URI scheme is defined under Section 7 of 132 + this document. Although 'acct' URIs take the form "user@host", the 133 + scheme is designed for the purpose of identification instead of 134 + interaction (regarding this distinction, see Section 1.2.2 of 135 + [RFC3986]). The "Internet resource" identified by an 'acct' URI is a 136 + user's account hosted at a service provider, where the service 137 + provider is typically associated with a DNS domain name. Thus, a 138 + particular 'acct' URI is formed by setting the "user" portion to the 139 + user's account name at the service provider and by setting the "host" 140 + portion to the DNS domain name of the service provider. 141 + 142 + Consider the case of a user with an account name of "foobar" on a 143 + microblogging service "status.example.net". It is taken as 144 + convention that the string "foobar@status.example.net" designates 145 + that account. This is expressed as a URI using the 'acct' scheme as 146 + "acct:foobar@status.example.net". 147 + 148 + A common scenario is for a user to register with a service provider 149 + using an identifier (such as an email address) that is associated 150 + with some other service provider. For example, a user with the email 151 + address "juliet@capulet.example" might register with a commerce 152 + website whose domain name is "shoppingsite.example". In order to use 153 + her email address as the localpart of the 'acct' URI, the at-sign 154 + character (U+0040) needs to be percent-encoded as described in 155 + [RFC3986]. Thus, the resulting 'acct' URI would be 156 + "acct:juliet%40capulet.example@shoppingsite.example". 157 + 158 + It is not assumed that an entity will necessarily be able to interact 159 + with a user's account using any particular application protocol, such 160 + as email; to enable such interaction, an entity would need to use the 161 + appropriate URI scheme for such a protocol, such as the 'mailto' 162 + scheme. While it might be true that the 'acct' URI minus the scheme 163 + name (e.g., "user@example.com" derived from "acct:user@example.com") 164 + can be reached via email or some other application protocol, that 165 + fact would be purely contingent and dependent upon the deployment 166 + practices of the provider. 167 + 168 + 169 + 170 + Saint-Andre Standards Track [Page 3] 171 + 172 + RFC 7565 The 'acct' URI Scheme May 2015 173 + 174 + 175 + Because an 'acct' URI enables abstract identification only and not 176 + interaction, this specification provides no method for dereferencing 177 + an 'acct' URI on its own, e.g., as the value of the 'href' attribute 178 + of an HTML anchor element. For example, there is no behavior 179 + specified in this document for an 'acct' URI used as follows: 180 + 181 + <a href='acct:bob@example.com'>find out more</a> 182 + 183 + Any protocol that uses 'acct' URIs is responsible for specifying how 184 + an 'acct' URI is employed in the context of that protocol (in 185 + particular, how it is dereferenced or resolved; see [RFC3986]). As a 186 + concrete example, an "Account Information" application of the 187 + WebFinger protocol [RFC7033] might take an 'acct' URI, resolve the 188 + host portion to find a WebFinger server, and then pass the 'acct' URI 189 + as a parameter in a WebFinger HTTP request for metadata (i.e., web 190 + links [RFC5988]) about the resource. For example: 191 + 192 + GET /.well-known/webfinger?resource=acct%3Abob%40example.com HTTP/1.1 193 + 194 + The service retrieves the metadata associated with the account 195 + identified by that URI and then provides that metadata to the 196 + requesting entity in an HTTP response. 197 + 198 + If an application needs to compare two 'acct' URIs (e.g., for 199 + purposes of authentication and authorization), it MUST do so using 200 + case normalization and percent-encoding normalization as specified in 201 + Sections 6.2.2.1 and 6.2.2.2 of [RFC3986]. 202 + 203 + 5. Security Considerations 204 + 205 + Because the 'acct' URI scheme does not directly enable interaction 206 + with a user's account at a service provider, direct security concerns 207 + are minimized. 208 + 209 + However, an 'acct' URI does provide proof of existence of the 210 + account; this implies that harvesting published 'acct' URIs could 211 + prove useful to spammers and similar attackers -- for example, if 212 + they can use an 'acct' URI to leverage more information about the 213 + account (e.g., via WebFinger) or if they can interact with protocol- 214 + specific URIs (such as 'mailto' URIs) whose user@host portion is the 215 + same as that of the 'acct' URI. 216 + 217 + In addition, protocols that make use of 'acct' URIs are responsible 218 + for defining security considerations related to such usage, e.g., the 219 + risks involved in dereferencing an 'acct' URI, the authentication and 220 + authorization methods that could be used to control access to 221 + personal data associated with a user's account at a service, and 222 + methods for ensuring the confidentiality of such information. 223 + 224 + 225 + 226 + Saint-Andre Standards Track [Page 4] 227 + 228 + RFC 7565 The 'acct' URI Scheme May 2015 229 + 230 + 231 + The use of percent-encoding allows a wider range of characters in 232 + account names but introduces some additional risks. Implementers are 233 + advised to disallow percent-encoded characters or sequences that 234 + would (1) result in space, null, control, or other characters that 235 + are otherwise forbidden, (2) allow unauthorized access to private 236 + data, or (3) lead to other security vulnerabilities. 237 + 238 + 6. Internationalization Considerations 239 + 240 + As specified in [RFC3986], the 'acct' URI scheme allows any character 241 + from the Unicode repertoire [Unicode] encoded as UTF-8 [RFC3629] and 242 + then percent-encoded into valid ASCII [RFC20]. Before applying any 243 + percent-encoding, an application MUST ensure the following about the 244 + string that is used as input to the URI-construction process: 245 + 246 + o The userpart consists only of Unicode code points that conform to 247 + the PRECIS IdentifierClass specified in [RFC7564]. 248 + 249 + o The host consists only of Unicode code points that conform to the 250 + rules specified in [RFC5892]. 251 + 252 + o Internationalized domain name (IDN) labels are encoded as A-labels 253 + [RFC5890]. 254 + 255 + 7. IANA Considerations 256 + 257 + In accordance with the guidelines and registration procedures for new 258 + URI schemes [RFC4395], this section provides the information needed 259 + to register the 'acct' URI scheme. 260 + 261 + URI Scheme Name: acct 262 + 263 + Status: permanent 264 + 265 + URI Scheme Syntax: The 'acct' URI syntax is defined here in 266 + Augmented Backus-Naur Form (ABNF) [RFC5234], borrowing the 'host', 267 + 'pct-encoded', 'sub-delims', and 'unreserved' rules from 268 + [RFC3986]: 269 + 270 + acctURI = "acct" ":" userpart "@" host 271 + userpart = unreserved / sub-delims 272 + 0*( unreserved / pct-encoded / sub-delims ) 273 + 274 + Note that additional rules regarding the strings that are used as 275 + input to construction of 'acct' URIs further limit the characters 276 + that can be percent-encoded; see the Encoding Considerations as 277 + well as Section 6 of this document. 278 + 279 + 280 + 281 + 282 + Saint-Andre Standards Track [Page 5] 283 + 284 + RFC 7565 The 'acct' URI Scheme May 2015 285 + 286 + 287 + URI Scheme Semantics: The 'acct' URI scheme identifies accounts 288 + hosted at service providers. It is used only for identification, 289 + not interaction. A protocol that employs the 'acct' URI scheme is 290 + responsible for specifying how an 'acct' URI is dereferenced in 291 + the context of that protocol. There is no media type associated 292 + with the 'acct' URI scheme. 293 + 294 + Encoding Considerations: See Section 6 of this document. 295 + 296 + Applications/Protocols That Use This URI Scheme Name: At the time of 297 + this writing, only the WebFinger protocol uses the 'acct' URI 298 + scheme. However, use is not restricted to the WebFinger protocol, 299 + and the scheme might be considered for use in other protocols. 300 + 301 + Interoperability Considerations: There are no known interoperability 302 + concerns related to use of the 'acct' URI scheme. 303 + 304 + Security Considerations: See Section 5 of this document. 305 + 306 + Contact: Peter Saint-Andre, peter@andyet.com 307 + 308 + Author/Change Controller: This scheme is registered under the IETF 309 + tree. As such, the IETF maintains change control. 310 + 311 + References: None. 312 + 313 + 8. References 314 + 315 + 8.1. Normative References 316 + 317 + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate 318 + Requirement Levels", BCP 14, RFC 2119, 319 + DOI 10.17487/RFC2119, March 1997, 320 + <http://www.rfc-editor.org/info/rfc2119>. 321 + 322 + [RFC3629] Yergeau, F., "UTF-8, a transformation format of 323 + ISO 10646", STD 63, RFC 3629, DOI 10.17487/RFC3629, 324 + November 2003, <http://www.rfc-editor.org/info/rfc3629>. 325 + 326 + [RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform 327 + Resource Identifier (URI): Generic Syntax", STD 66, 328 + RFC 3986, DOI 10.17487/RFC3986, January 2005, 329 + <http://www.rfc-editor.org/info/rfc3986>. 330 + 331 + [RFC5234] Crocker, D., Ed., and P. Overell, "Augmented BNF for 332 + Syntax Specifications: ABNF", STD 68, RFC 5234, 333 + DOI 10.17487/RFC5234, January 2008, 334 + <http://www.rfc-editor.org/info/rfc5234>. 335 + 336 + 337 + 338 + Saint-Andre Standards Track [Page 6] 339 + 340 + RFC 7565 The 'acct' URI Scheme May 2015 341 + 342 + 343 + [RFC5890] Klensin, J., "Internationalized Domain Names for 344 + Applications (IDNA): Definitions and Document Framework", 345 + RFC 5890, DOI 10.17487/RFC5890, August 2010, 346 + <http://www.rfc-editor.org/info/rfc5890>. 347 + 348 + [RFC5892] Faltstrom, P., Ed., "The Unicode Code Points and 349 + Internationalized Domain Names for Applications (IDNA)", 350 + RFC 5892, DOI 10.17487/RFC5892, August 2010, 351 + <http://www.rfc-editor.org/info/rfc5892>. 352 + 353 + [RFC7564] Saint-Andre, P. and M. Blanchet, "PRECIS Framework: 354 + Preparation, Enforcement, and Comparison of 355 + Internationalized Strings in Application Protocols", 356 + RFC 7564, DOI 10.17487/RFC7564, May 2015, 357 + <http://www.rfc-editor.org/info/rfc7564>. 358 + 359 + [Unicode] The Unicode Consortium, "The Unicode Standard", 360 + <http://www.unicode.org/versions/latest/>. 361 + 362 + 8.2. Informative References 363 + 364 + [RFC20] Cerf, V., "ASCII format for network interchange", STD 80, 365 + RFC 20, DOI 10.17487/RFC0020, October 1969, 366 + <http://www.rfc-editor.org/info/rfc20>. 367 + 368 + [RFC4395] Hansen, T., Hardie, T., and L. Masinter, "Guidelines and 369 + Registration Procedures for New URI Schemes", BCP 35, 370 + RFC 4395, DOI 10.17487/RFC4395, February 2006, 371 + <http://www.rfc-editor.org/info/rfc4395>. 372 + 373 + [RFC5988] Nottingham, M., "Web Linking", RFC 5988, 374 + DOI 10.17487/RFC5988, October 2010, 375 + <http://www.rfc-editor.org/info/rfc5988>. 376 + 377 + [RFC6068] Duerst, M., Masinter, L., and J. Zawinski, "The 'mailto' 378 + URI Scheme", RFC 6068, DOI 10.17487/RFC6068, October 2010, 379 + <http://www.rfc-editor.org/info/rfc6068>. 380 + 381 + [RFC7033] Jones, P., Salgueiro, G., Jones, M., and J. Smarr, 382 + "WebFinger", RFC 7033, DOI 10.17487/RFC7033, 383 + September 2013, <http://www.rfc-editor.org/info/rfc7033>. 384 + 385 + [RFC7230] Fielding, R., Ed., and J. Reschke, Ed., "Hypertext 386 + Transfer Protocol (HTTP/1.1): Message Syntax and Routing", 387 + RFC 7230, DOI 10.17487/RFC7230, June 2014, 388 + <http://www.rfc-editor.org/info/rfc7230>. 389 + 390 + 391 + 392 + 393 + 394 + Saint-Andre Standards Track [Page 7] 395 + 396 + RFC 7565 The 'acct' URI Scheme May 2015 397 + 398 + 399 + Acknowledgements 400 + 401 + The 'acct' URI scheme was originally proposed during work on the 402 + WebFinger protocol; special thanks are due to Blaine Cook, Brad 403 + Fitzpatrick, and Eran Hammer-Lahav for their early work on the 404 + concept (which in turn was partially inspired by work on Extensible 405 + Resource Identifiers at OASIS). The scheme was first formally 406 + specified in [RFC7033]; the authors of that specification (Paul 407 + Jones, Gonzalo Salgueiro, and Joseph Smarr) are gratefully 408 + acknowledged. Thanks are also due to Stephane Bortzmeyer, Melvin 409 + Carvalho, Martin Duerst, Graham Klyne, Barry Leiba, Subramanian 410 + Moonesamy, Evan Prodromou, James Snell, and various participants in 411 + the IETF APPSAWG for their feedback. Meral Shirazipour completed a 412 + Gen-ART review. Dave Cridland completed an AppsDir review and is 413 + gratefully acknowledged for providing proposed text that was 414 + incorporated into Sections 3 and 5. IESG comments from Richard 415 + Barnes, Adrian Farrel, Stephen Farrell, Barry Leiba, Pete Resnick, 416 + and Sean Turner also led to improvements in the specification. 417 + 418 + Author's Address 419 + 420 + Peter Saint-Andre 421 + 422 + EMail: peter@andyet.com 423 + URI: https://andyet.com/ 424 + 425 + 426 + 427 + 428 + 429 + 430 + 431 + 432 + 433 + 434 + 435 + 436 + 437 + 438 + 439 + 440 + 441 + 442 + 443 + 444 + 445 + 446 + 447 + 448 + 449 + 450 + Saint-Andre Standards Track [Page 8] 451 +
+3
test/dune
··· 1 + (test 2 + (name test_webfinger) 3 + (libraries webfinger alcotest))
+300
test/test_webfinger.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Tests for the WebFinger library. *) 7 + 8 + (** Helper module for substring check *) 9 + module String = struct 10 + include String 11 + let is_substring ~sub s = 12 + let rec check i = 13 + if i + String.length sub > String.length s then false 14 + else if String.sub s i (String.length sub) = sub then true 15 + else check (i + 1) 16 + in 17 + check 0 18 + end 19 + 20 + let test_jrd_roundtrip () = 21 + let jrd = Webfinger.Jrd.make 22 + ~subject:"acct:user@example.com" 23 + ~aliases:["https://example.com/users/user"] 24 + ~properties:[ 25 + ("http://example.com/ns/role", Some "admin"); 26 + ("http://example.com/ns/verified", None); 27 + ] 28 + ~links:[ 29 + Webfinger.Link.make 30 + ~rel:"self" 31 + ~type_:"application/activity+json" 32 + ~href:"https://example.com/users/user" 33 + ~titles:[("en", "User Profile"); ("und", "Profile")] 34 + ~properties:[("http://example.com/ns/verified", Some "true")] 35 + (); 36 + Webfinger.Link.make 37 + ~rel:"http://webfinger.net/rel/profile-page" 38 + ~type_:"text/html" 39 + ~href:"https://example.com/@user" 40 + (); 41 + ] 42 + () 43 + in 44 + let json = Webfinger.Jrd.to_string jrd in 45 + match Webfinger.Jrd.of_string json with 46 + | Error e -> 47 + Alcotest.fail (Webfinger.error_to_string e) 48 + | Ok parsed -> 49 + Alcotest.(check (option string)) "subject" (Webfinger.Jrd.subject jrd) (Webfinger.Jrd.subject parsed); 50 + Alcotest.(check int) "aliases count" (List.length (Webfinger.Jrd.aliases jrd)) (List.length (Webfinger.Jrd.aliases parsed)); 51 + Alcotest.(check int) "links count" (List.length (Webfinger.Jrd.links jrd)) (List.length (Webfinger.Jrd.links parsed)) 52 + 53 + let test_jrd_minimal () = 54 + let json = {|{}|} in 55 + match Webfinger.Jrd.of_string json with 56 + | Error e -> 57 + Alcotest.fail (Webfinger.error_to_string e) 58 + | Ok jrd -> 59 + Alcotest.(check (option string)) "subject" None (Webfinger.Jrd.subject jrd); 60 + Alcotest.(check int) "aliases" 0 (List.length (Webfinger.Jrd.aliases jrd)); 61 + Alcotest.(check int) "properties" 0 (List.length (Webfinger.Jrd.properties jrd)); 62 + Alcotest.(check int) "links" 0 (List.length (Webfinger.Jrd.links jrd)) 63 + 64 + let test_jrd_with_all_fields () = 65 + let json = {|{ 66 + "subject": "acct:user@example.com", 67 + "aliases": ["https://example.com/~user", "https://example.com/users/user"], 68 + "properties": { 69 + "http://example.com/ns/role": "admin", 70 + "http://example.com/ns/nullable": null 71 + }, 72 + "links": [ 73 + { 74 + "rel": "self", 75 + "type": "application/activity+json", 76 + "href": "https://example.com/users/user", 77 + "titles": { 78 + "en": "User's ActivityPub Profile", 79 + "und": "Profile" 80 + }, 81 + "properties": { 82 + "http://example.com/ns/verified": "true" 83 + } 84 + }, 85 + { 86 + "rel": "http://webfinger.net/rel/profile-page", 87 + "type": "text/html", 88 + "href": "https://example.com/@user" 89 + } 90 + ] 91 + }|} in 92 + match Webfinger.Jrd.of_string json with 93 + | Error e -> 94 + Alcotest.fail (Webfinger.error_to_string e) 95 + | Ok jrd -> 96 + Alcotest.(check (option string)) "subject" 97 + (Some "acct:user@example.com") (Webfinger.Jrd.subject jrd); 98 + Alcotest.(check int) "aliases count" 2 (List.length (Webfinger.Jrd.aliases jrd)); 99 + Alcotest.(check int) "properties count" 2 (List.length (Webfinger.Jrd.properties jrd)); 100 + Alcotest.(check int) "links count" 2 (List.length (Webfinger.Jrd.links jrd)); 101 + 102 + (* Check first link *) 103 + let link = List.hd (Webfinger.Jrd.links jrd) in 104 + Alcotest.(check string) "link rel" "self" (Webfinger.Link.rel link); 105 + Alcotest.(check (option string)) "link type" (Some "application/activity+json") (Webfinger.Link.type_ link); 106 + Alcotest.(check (option string)) "link href" (Some "https://example.com/users/user") (Webfinger.Link.href link); 107 + Alcotest.(check int) "link titles" 2 (List.length (Webfinger.Link.titles link)); 108 + Alcotest.(check int) "link properties" 1 (List.length (Webfinger.Link.properties link)) 109 + 110 + let test_host_extraction () = 111 + (* acct: URIs *) 112 + Alcotest.(check (result string Alcotest.reject)) "acct URI" 113 + (Ok "example.com") 114 + (Webfinger.host_of_resource "acct:user@example.com"); 115 + 116 + Alcotest.(check (result string Alcotest.reject)) "acct URI with port" 117 + (Ok "example.com:8080") 118 + (Webfinger.host_of_resource "acct:user@example.com:8080"); 119 + 120 + (* https: URIs *) 121 + Alcotest.(check (result string Alcotest.reject)) "https URI" 122 + (Ok "example.com") 123 + (Webfinger.host_of_resource "https://example.com/users/user"); 124 + 125 + (* Invalid URIs *) 126 + let invalid = Webfinger.host_of_resource "acct:noatsign" in 127 + Alcotest.(check bool) "invalid acct URI" true (Result.is_error invalid) 128 + 129 + let test_webfinger_url () = 130 + let url = Webfinger.webfinger_url ~resource:"acct:user@example.com" "example.com" in 131 + (* Uri library doesn't percent-encode @ and : in query params - both forms are valid *) 132 + Alcotest.(check string) "basic URL" 133 + "https://example.com/.well-known/webfinger?resource=acct:user@example.com" 134 + url; 135 + 136 + let url_with_rels = Webfinger.webfinger_url 137 + ~resource:"acct:user@example.com" 138 + ~rels:["self"; "http://webfinger.net/rel/profile-page"] 139 + "example.com" 140 + in 141 + Alcotest.(check bool) "URL has rel params" 142 + true 143 + (String.length url_with_rels > String.length url) 144 + 145 + let test_find_link () = 146 + let jrd = Webfinger.Jrd.make 147 + ~links:[ 148 + Webfinger.Link.make ~rel:"self" ~href:"https://example.com/1" (); 149 + Webfinger.Link.make ~rel:"alternate" ~href:"https://example.com/2" (); 150 + Webfinger.Link.make ~rel:"self" ~href:"https://example.com/3" (); 151 + ] 152 + () 153 + in 154 + (* find_link returns first match *) 155 + match Webfinger.Jrd.find_link ~rel:"self" jrd with 156 + | None -> Alcotest.fail "Expected to find link" 157 + | Some link -> 158 + Alcotest.(check (option string)) "href" 159 + (Some "https://example.com/1") (Webfinger.Link.href link); 160 + 161 + (* find_links returns all matches *) 162 + let links = Webfinger.Jrd.find_links ~rel:"self" jrd in 163 + Alcotest.(check int) "found all self links" 2 (List.length links); 164 + 165 + (* Non-existent rel *) 166 + Alcotest.(check (option Alcotest.reject)) "no match" 167 + None 168 + (Webfinger.Jrd.find_link ~rel:"nonexistent" jrd) 169 + 170 + let test_link_title () = 171 + let link = Webfinger.Link.make 172 + ~rel:"self" 173 + ~titles:[("en", "English Title"); ("de", "German Title"); ("und", "Default")] 174 + () 175 + in 176 + Alcotest.(check (option string)) "English" 177 + (Some "English Title") 178 + (Webfinger.Link.title ~lang:"en" link); 179 + 180 + Alcotest.(check (option string)) "German" 181 + (Some "German Title") 182 + (Webfinger.Link.title ~lang:"de" link); 183 + 184 + (* Falls back to "und" *) 185 + Alcotest.(check (option string)) "French falls back to und" 186 + (Some "Default") 187 + (Webfinger.Link.title ~lang:"fr" link); 188 + 189 + (* Default is "und" *) 190 + Alcotest.(check (option string)) "default is und" 191 + (Some "Default") 192 + (Webfinger.Link.title link) 193 + 194 + (** {1 Acct URI Tests (RFC 7565)} *) 195 + 196 + let test_acct_basic () = 197 + (* Basic parsing *) 198 + let acct = Webfinger.Acct.of_string_exn "acct:foobar@status.example.net" in 199 + Alcotest.(check string) "userpart" "foobar" (Webfinger.Acct.userpart acct); 200 + Alcotest.(check string) "host" "status.example.net" (Webfinger.Acct.host acct); 201 + 202 + (* Roundtrip *) 203 + let s = Webfinger.Acct.to_string acct in 204 + Alcotest.(check string) "roundtrip" "acct:foobar@status.example.net" s 205 + 206 + let test_acct_make () = 207 + let acct = Webfinger.Acct.make ~userpart:"user" ~host:"example.com" in 208 + Alcotest.(check string) "userpart" "user" (Webfinger.Acct.userpart acct); 209 + Alcotest.(check string) "host" "example.com" (Webfinger.Acct.host acct); 210 + Alcotest.(check string) "to_string" "acct:user@example.com" (Webfinger.Acct.to_string acct) 211 + 212 + let test_acct_percent_encoding () = 213 + (* Email address as userpart - @ must be percent-encoded per RFC 7565 Section 4 *) 214 + let acct = Webfinger.Acct.of_string_exn "acct:juliet%40capulet.example@shoppingsite.example" in 215 + Alcotest.(check string) "userpart with @" "juliet@capulet.example" (Webfinger.Acct.userpart acct); 216 + Alcotest.(check string) "host" "shoppingsite.example" (Webfinger.Acct.host acct); 217 + 218 + (* Roundtrip: the @ in the userpart should be percent-encoded *) 219 + let s = Webfinger.Acct.to_string acct in 220 + Alcotest.(check bool) "roundtrip contains %40" 221 + true (String.is_substring ~sub:"%40" s) 222 + 223 + let test_acct_make_with_at () = 224 + (* Creating an acct with @ in userpart should auto-encode *) 225 + let acct = Webfinger.Acct.make ~userpart:"juliet@capulet.example" ~host:"shoppingsite.example" in 226 + let s = Webfinger.Acct.to_string acct in 227 + (* The @ in the userpart should be percent-encoded *) 228 + Alcotest.(check bool) "@ is encoded" 229 + true (String.is_substring ~sub:"%40" s); 230 + (* But we can still get the decoded userpart back *) 231 + Alcotest.(check string) "userpart preserved" "juliet@capulet.example" (Webfinger.Acct.userpart acct) 232 + 233 + let test_acct_host_normalization () = 234 + (* Host should be normalized to lowercase *) 235 + let acct1 = Webfinger.Acct.of_string_exn "acct:user@Example.COM" in 236 + let acct2 = Webfinger.Acct.of_string_exn "acct:user@example.com" in 237 + Alcotest.(check bool) "equal after host normalization" true (Webfinger.Acct.equal acct1 acct2); 238 + Alcotest.(check string) "host is lowercase" "example.com" (Webfinger.Acct.host acct1) 239 + 240 + let test_acct_equality () = 241 + let acct1 = Webfinger.Acct.of_string_exn "acct:User@Example.COM" in 242 + let acct2 = Webfinger.Acct.of_string_exn "acct:User@example.com" in 243 + let acct3 = Webfinger.Acct.of_string_exn "acct:user@example.com" in 244 + 245 + (* Same userpart, different host case - equal *) 246 + Alcotest.(check bool) "same user, diff host case" true (Webfinger.Acct.equal acct1 acct2); 247 + 248 + (* Different userpart - not equal (userpart is case-sensitive) *) 249 + Alcotest.(check bool) "different userpart" false (Webfinger.Acct.equal acct2 acct3) 250 + 251 + let test_acct_invalid () = 252 + (* Missing scheme *) 253 + Alcotest.(check bool) "missing scheme" 254 + true (Result.is_error (Webfinger.Acct.of_string "user@example.com")); 255 + 256 + (* Missing @ *) 257 + Alcotest.(check bool) "missing @" 258 + true (Result.is_error (Webfinger.Acct.of_string "acct:userexample.com")); 259 + 260 + (* Empty userpart *) 261 + Alcotest.(check bool) "empty userpart" 262 + true (Result.is_error (Webfinger.Acct.of_string "acct:@example.com")); 263 + 264 + (* Empty host *) 265 + Alcotest.(check bool) "empty host" 266 + true (Result.is_error (Webfinger.Acct.of_string "acct:user@")) 267 + 268 + let test_acct_webfinger_url () = 269 + let acct = Webfinger.Acct.of_string_exn "acct:user@example.com" in 270 + let url = Webfinger.webfinger_url_acct acct () in 271 + Alcotest.(check string) "webfinger URL" 272 + "https://example.com/.well-known/webfinger?resource=acct:user@example.com" 273 + url 274 + 275 + let () = 276 + Alcotest.run "webfinger" [ 277 + "jrd", [ 278 + Alcotest.test_case "roundtrip" `Quick test_jrd_roundtrip; 279 + Alcotest.test_case "minimal" `Quick test_jrd_minimal; 280 + Alcotest.test_case "all fields" `Quick test_jrd_with_all_fields; 281 + ]; 282 + "url", [ 283 + Alcotest.test_case "host extraction" `Quick test_host_extraction; 284 + Alcotest.test_case "webfinger URL" `Quick test_webfinger_url; 285 + ]; 286 + "accessors", [ 287 + Alcotest.test_case "find_link" `Quick test_find_link; 288 + Alcotest.test_case "link_title" `Quick test_link_title; 289 + ]; 290 + "acct", [ 291 + Alcotest.test_case "basic" `Quick test_acct_basic; 292 + Alcotest.test_case "make" `Quick test_acct_make; 293 + Alcotest.test_case "percent encoding" `Quick test_acct_percent_encoding; 294 + Alcotest.test_case "make with @" `Quick test_acct_make_with_at; 295 + Alcotest.test_case "host normalization" `Quick test_acct_host_normalization; 296 + Alcotest.test_case "equality" `Quick test_acct_equality; 297 + Alcotest.test_case "invalid" `Quick test_acct_invalid; 298 + Alcotest.test_case "webfinger URL" `Quick test_acct_webfinger_url; 299 + ]; 300 + ]
+43
webfinger.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "RFC 7033 WebFinger and RFC 7565 acct URI scheme for OCaml" 4 + description: """ 5 + A complete implementation of RFC 7033 (WebFinger) and RFC 7565 (acct URI scheme) 6 + for discovering information about resources identified by URIs. Includes type-safe 7 + JSON Resource Descriptor (JRD) encoding/decoding using jsont, an Eio-based HTTP 8 + client using the requests library, and a command-line tool for WebFinger lookups.""" 9 + maintainer: ["Anil Madhavapeddy <anil@recoil.org>"] 10 + authors: ["Anil Madhavapeddy"] 11 + license: "ISC" 12 + homepage: "https://tangled.org/@anil.recoil.org/ocaml-webfinger" 13 + bug-reports: "https://tangled.org/@anil.recoil.org/ocaml-webfinger/issues" 14 + depends: [ 15 + "ocaml" {>= "5.2.0"} 16 + "dune" {>= "3.20" & >= "3.0"} 17 + "jsont" {>= "0.1.0"} 18 + "jsont-bytesrw" {>= "0.1.0"} 19 + "eio" {>= "1.0"} 20 + "eio_main" {>= "1.0"} 21 + "requests" {>= "0.1.0"} 22 + "uri" {>= "4.0.0"} 23 + "cmdliner" {>= "1.2.0"} 24 + "logs" {>= "0.7.0"} 25 + "fmt" {>= "0.9.0"} 26 + "odoc" {with-doc} 27 + "alcotest" {with-test} 28 + ] 29 + build: [ 30 + ["dune" "subst"] {dev} 31 + [ 32 + "dune" 33 + "build" 34 + "-p" 35 + name 36 + "-j" 37 + jobs 38 + "@install" 39 + "@runtest" {with-test} 40 + "@doc" {with-doc} 41 + ] 42 + ] 43 + x-maintenance-intent: ["(latest)"]