Check-in [54a2eae9c2]
Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Add caps and mtime columns, some doc fixes. |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
54a2eae9c279c043d5f2d8c25515ff00 |
User & Date: | mario 2021-04-10 22:45:09 |
Context
2021-04-10
| ||
22:45 | charset= instead of encoding= check-in: d059f0486e user: mario tags: trunk | |
22:45 | Add caps and mtime columns, some doc fixes. check-in: 54a2eae9c2 user: mario tags: trunk | |
2021-04-09
| ||
04:34 | micropub: expanded properties declaration check-in: 2a5697b856 user: mario tags: trunk | |
Changes
Changes to extroot/auth.
︙ | ︙ | |||
24 25 26 27 28 29 30 | # # ## SETUP # # So this requires installation in fossils extroot: (internal cgi scripts), # and might not work in chroot jails (due to php-cgi shebang). Copy the script # into ext/, preferrably without .php or .cgi extension, and chmod +x it. # | < < < | | | | | < < > | > | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | # # ## SETUP # # So this requires installation in fossils extroot: (internal cgi scripts), # and might not work in chroot jails (due to php-cgi shebang). Copy the script # into ext/, preferrably without .php or .cgi extension, and chmod +x it. # # User accounts are required to list an url in `homepages` or `info` column. # Each needs at least one, so requests can't be used to impersonate. # # Afterwards configure your homepage to allow its use as IndieAuth id: # <link rel=authorization_endpoint href="http://fossil.domain/ext/auth"> # # # ## TOKEN # # The fx_auth table contains individual columns now to record previous # authorization requests. Most of which is unnecessary to keep after the # initial request. This is largely for debugging. # # The /token endpoint will use the same entries however, and upgrade from # `code` to `token` on request. Which the /micropub handler then verifies. # # # ## PROTOCOL # # Authorization request: # ?me=https://user.example.org/ # &client_id=http://app.example.com/ |
︙ | ︙ | |||
66 67 68 69 70 71 72 73 | # Response: # Location: https://app.example.com/login/callback?code=...&state=1234567890 # # Verification request: # ?code=$1y$..... # &client_id=http://app.example.com/ # &redirect_uri=http://app.example.com/login/callback # Response: | > | | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | # Response: # Location: https://app.example.com/login/callback?code=...&state=1234567890 # # Verification request: # ?code=$1y$..... # &client_id=http://app.example.com/ # &redirect_uri=http://app.example.com/login/callback # &code_verifier=raw-pre-sha256-cnt # Response: # { "me": "https://user.example.org/", "scope": "...", "access_token": "..." } # # if ($_REQUEST["dbg"]) { error_reporting(E_ALL); ini_set("display_errors", 1); } |
︙ | ︙ | |||
96 97 98 99 100 101 102 | return $db->query($sql); } } function create_table() { db("CREATE TABLE IF NOT EXISTS `fx_auth` ( -- IndieAuth token table `code` TEXT, -- OAuth authorization code `type` TEXT, -- one of id,code,token,revoked | | > > | > > > > > > > > > > > > > > > > > > > > > > > > > | | | | 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | return $db->query($sql); } } function create_table() { db("CREATE TABLE IF NOT EXISTS `fx_auth` ( -- IndieAuth token table `code` TEXT, -- OAuth authorization code `type` TEXT, -- one of id,code,token,revoked `scope` TEXT, -- requested permissions (create,update,delete) `caps` TEXT, -- fossil permissions (askmnw3C) `login` TEXT, -- fossil user account `me` TEXT, -- https://userwebid.example.org/ `client_id` TEXT, -- https://remoteapp.example.com/ `redirect_uri` TEXT, -- https://app/login/callback `state` TEXT, -- remote session id (12345..) `code_challenge` TEXT, -- pre-hashed secret for later token req `code_challenge_m` TEXT,-- hash method for secret `mtime` INT, -- token entry last modified `expires` INT -- code valid until timestamp )"); } #-- fossil HTML output function page_html($html) { header("Content-Type: text/html; charset=utf-8"); $svg = <<<SVG <svg style='float:left; margin-right: 30pt;' width="280" height="268" version="1.1" viewBox="0 0 56.048 53.779" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <linearGradient id="linearGradient2066" x1="63.409" x2="36.085" y1="109.16" y2="82.295" gradientTransform="matrix(3.7795 0 0 3.7795 -57.385 -274.06)" gradientUnits="userSpaceOnUse"> <stop stop-color="#7aba0f" offset="0"/> <stop stop-color="#bfe47f" offset="1"/> </linearGradient> <linearGradient id="linearGradient1185" x1="52.613" x2="47.211" y1="84.095" y2="93.583" gradientUnits="userSpaceOnUse"> <stop stop-color="#fff" stop-opacity=".9" offset="0"/> <stop stop-color="#f9faf9" stop-opacity=".5" offset="1"/> </linearGradient> <filter id="filter1275" x="-.024063" y="-.023937" width="1.0481" height="1.0479" color-interpolation-filters="sRGB"> <feGaussianBlur stdDeviation="0.12728711"/> </filter> </defs> <g transform="translate(-15.183 -72.511)"> <g transform="matrix(1.6139 0 0 1.5192 51.943 -52.074)"> <path d="m7.8389 85.485-3.8743 6.0476c-23.226-3.1157-33.318 9.3314-8.599 22.962-24.906-4.8059-27.229-30.998 12.473-29.01z" fill="#fe230f"/> <path d="m4.7884 90.276-2.5066 4.8015c-21.825-2.5224-22.803 6.515-7.0408 19.345-14.144-5.3082-27.76-25.757 9.5474-24.147z" fill="#fe5d0f"/> <path d="m2.399 94.78-2.1773 3.8912c-18.675-1.7607-14.894 3.7679-5.0765 15.772-7.8444-5.7192-23.168-22.385 7.2538-19.663z" fill="#fea70f"/> </g> <path transform="matrix(.26458 0 0 .26458 15.183 72.511)" d="m169.4 3.3359c-24.036 0.16796-48.072 0.33592-72.107 0.50391-9.4009 42.186-18.802 84.372-28.203 126.56 11.159 17.745 24.195 38.517 37.898 54.234 0.52981-18.818 1.06-37.635 1.5898-56.453 7.7311-3.2461 15.462-6.4922 23.193-9.7383 8.084 3.3861 16.168 6.7721 24.252 10.158-0.40041 22.532 2.4212 45.099 0.67383 67.592-3.1046 4.8346 4.9086 1.9182 7.7676 2.7148 14.439 0.00756 28.878 0.01783 43.316 0.02539-12.794-65.199-25.587-130.4-38.381-195.6zm-34.188 28.412c17.222 0.54857 30.629 19.534 24.859 35.994-4.6736 17.401-27.745 25.447-42.223 14.717-15.248-9.6074-16.115-34.025-1.584-44.688 4.8603-3.8773 11.053-6.0331 17.27-6.0176 0.56296-0.021376 1.1222-0.023555 1.6777-0.005859zm-75.895 142.51c-1.9066 8.556-3.8141 17.112-5.7207 25.668l52.961-0.01172c0.11707-4.1582 0.23449-8.3164 0.35157-12.475-15.189-3.0869-32.389-7.5965-47.592-13.182zm95.695 24.646c1.2368 0 0.37266 0 0 0z" fill="url(#linearGradient2066)" stroke="#4a5848" stroke-width="6.6709"/> <ellipse cx="50.446" cy="88.237" rx="6.3477" ry="6.3811" fill="url(#linearGradient1185)" filter="url(#filter1275)" opacity=".9"/> </g> </svg> <!--svg height=270 width=215 style='float:left; margin-right: 30pt;' viewBox='0 0 42.967861 53.77858'> <g transform='translate(-26.926707,-72.244048)' id='layer1'> <path id='path828' d='m 58.667032,73.126526 c -6.35947,0.04444 -12.71895,0.08888 -19.078418,0.133327 -3.853683,17.29335 -7.707367,34.586687 -11.56105,51.880037 4.67086,-10e-4 9.341719,-0.002 14.012578,-0.003 0.17811,-6.32623 0.35623,-12.65246 0.53434,-18.97869 2.04552,-0.85886 4.09104,-1.71773 6.13657,-2.57659 2.13889,0.8959 4.27777,1.79179 6.41666,2.68769 -0.10594,5.96161 0.64059,11.93241 0.17825,17.88368 -0.82143,1.27915 1.29889,0.50748 2.05533,0.71827 3.82023,0.002 7.64045,0.005 11.46067,0.007 -3.38498,-17.25073 -6.76995,-34.501457 -10.15493,-51.752194 z m -9.48934,7.518922 c 4.76639,-0.180988 8.59732,5.026339 7.02166,9.521985 -1.23655,4.60395 -7.34109,6.7331 -11.17174,3.89414 -4.034474,-2.54196 -4.263284,-9.002574 -0.41875,-11.82358 1.28595,-1.025868 2.92405,-1.596645 4.56883,-1.592545 z m 5.68285,44.224172 c 0.32724,0 0.0986,0 0,0 z' style='fill:#aeea47;fill-opacity:1;stroke:#4a5848;stroke-width:1.76499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99456518' /> </g></svg--> SVG; print("<div class='fossil-doc' data-title='IndieAuth'>\n$svg\n$html\n</div>"); } function missing_param($name) { die(page_html("<h2>Missing input</h2><p>URL lacks <code>&$name=</code> parameter.<p>Can't process as IndieAuth/OAuth request.")); } function page_md($text) { header("Content-Type: text/x-markdown; charset=utf-8"); print($text); } function h($s) { return htmlspecialchars($s, ENT_QUOTES|ENT_HTML5, "UTF-8"); } |
︙ | ︙ | |||
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | } #-- initial authorization request function process_request() { # input params $user = $_SERVER["FOSSIL_USER"]; $secret = $_SERVER["FOSSIL_NONCE"]; $me = $_REQUEST["me"] or missing_param("me"); $client_id = $_REQUEST["client_id"] or missing_param("client_id"); $redirect_uri = $_REQUEST["redirect_uri"] or missing_param("redirect_uri"); $state = $_REQUEST["state"] ?: ""; $code_challenge = $_REQUEST["code_challenge"] ?: ""; $code_challenge_m = $_REQUEST["code_challenge_method"] ?: "S256"; $response_type = $_REQUEST["response_type"] ?: "id"; $h = "h"; # check if $me is allowed if (!allowed_identity($user, $me, $may_autologin)) { return page_html("<h2>Invalid identity</h2> User doesn't have '{$h($me)}' reserved in user table."); } # new code // hashing the properties is a bit overkill, any random id would suffice $code = password_hash("$me/$client_id/$state/$secret", PASSWORD_DEFAULT); db(" INSERT INTO fx_auth | > | | > | | 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | } #-- initial authorization request function process_request() { # input params $user = $_SERVER["FOSSIL_USER"]; $caps = preg_replace("/[^abcfhikkmnsw3C]/", "", $_SERVER["FOSSIL_CAPABILITIES"]); $secret = $_SERVER["FOSSIL_NONCE"]; $me = $_REQUEST["me"] or missing_param("me"); $client_id = $_REQUEST["client_id"] or missing_param("client_id"); $redirect_uri = $_REQUEST["redirect_uri"] or missing_param("redirect_uri"); $state = $_REQUEST["state"] ?: ""; $code_challenge = $_REQUEST["code_challenge"] ?: ""; $code_challenge_m = $_REQUEST["code_challenge_method"] ?: "S256"; $response_type = $_REQUEST["response_type"] ?: "id"; $h = "h"; # check if $me is allowed if (!allowed_identity($user, $me, $may_autologin)) { return page_html("<h2>Invalid identity</h2> User doesn't have '{$h($me)}' reserved in user table."); } # new code // hashing the properties is a bit overkill, any random id would suffice $code = password_hash("$me/$client_id/$state/$secret", PASSWORD_DEFAULT); db(" INSERT INTO fx_auth (`code`, `type`, `caps`, `login`, `me`, `client_id`, `redirect_uri`, `state`, `code_challenge`, `code_challenge_m`, `mtime`, `expires`) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [$code, $response_type, $caps, $user, $me, $client_id, $redirect_uri, $state, $code_challenge, $code_challenge_m, time(), time()+300] ); # construct confirmation+redirect url $url= $redirect_uri . (strstr($redirect_uri, "?") ? "&" : "?") . "code=" . urlencode($code) . "&state=" . urlencode($state); |
︙ | ︙ | |||
301 302 303 304 305 306 307 | #-- run if (empty($_POST["redirect_target"]) and !empty($_REQUEST["code"])) { # ?code=… when the remote app verifies the response verify_code(); } elseif (empty($_SERVER["FOSSIL_USER"])) { # user must be signed in at this point page_html("<h2>Not logged in</h2>\n Request can't be authorized, unless you're <a href='../login'>logged in</a>."); } | < > | 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 | #-- run if (empty($_POST["redirect_target"]) and !empty($_REQUEST["code"])) { # ?code=… when the remote app verifies the response verify_code(); } elseif (empty($_SERVER["FOSSIL_USER"])) { # user must be signed in at this point page_html("<h2>Not logged in</h2>\n Request can't be authorized, unless you're <a href='../login'>logged in</a>."); } elseif (!empty($_REQUEST["me"])) { # ?me=… starts an authorization request process_request(); } elseif (!empty($_POST["confirm"])) { # ?redirect_target=… for confirmation button confirm(); } else { db("SELECT 1"); page_html(" <h3>Authorization endpoint</h3> There was no ?code= or ?me= parameter,<br> so not an actual Indie/OAuth request. <ul> <li>The <code>fx_auth</code> table is now configured. <li>Users still need to register a web address in the info/homepage field. (See <a href='user_config'>ext/user_config</a>.) <li>And then declare this endpoint on their personal homepage:<br> |
︙ | ︙ |
Changes to extroot/token.
︙ | ︙ | |||
10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # state: untested # depends: php:sqlite # doc: https://indieweb.org/obtaining-an-access-token # config: - # # Counterpart to the `auth` cgi extension. This basically just # upgrades the authorization code to an access token internally. # # Request: POST .../token # ?grant_type=authorization_code # &me=https://userwebid.example.org/ # &code=$2y... # &redirect_uri=http://app.example.com/login/callback # &client_id=https://app.example.com | > | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | # state: untested # depends: php:sqlite # doc: https://indieweb.org/obtaining-an-access-token # config: - # # Counterpart to the `auth` cgi extension. This basically just # upgrades the authorization code to an access token internally. # The $2y$-hash token stays, the `fx_auth.type` becomes 'token'. # # Request: POST .../token # ?grant_type=authorization_code # &me=https://userwebid.example.org/ # &code=$2y... # &redirect_uri=http://app.example.com/login/callback # &client_id=https://app.example.com |
︙ | ︙ | |||
119 120 121 122 123 124 125 | elseif ($client_id != $token["client_id"]) { json_response(["error" => "access_denied", "error_description" => "wrong client_id"]); } elseif (in_array($token["type"], ["id", "revoked"]) or empty($token["scope"])) { json_response(["error" => "invalid_scope", "error_description" => "authorization code (response_type=id) not useable for access token upgrade"]); } else { | | | | 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | elseif ($client_id != $token["client_id"]) { json_response(["error" => "access_denied", "error_description" => "wrong client_id"]); } elseif (in_array($token["type"], ["id", "revoked"]) or empty($token["scope"])) { json_response(["error" => "invalid_scope", "error_description" => "authorization code (response_type=id) not useable for access token upgrade"]); } else { db("UPDATE fx_auth SET `type`=?, mtime=?, expires=? WHERE code=?", ["token", time(), time()+3600, $code]); json_response([ "access_token" => $code, #substr($code, 7), "token_type" => "Bearer", "scope" => $token["scope"], "me" => $token["me"], ]); } } function fix_code(&$code) { if (strpos($code, '$2y$10$') !== 0) { $code = '$2y$10$' . $code; } } #-- test validity of Authorization: Bearer TOKENCODE function verify_bearer($code) { fix_code($code); # find token clean_expired_token(); |
︙ | ︙ |