Collection of themes/skins for the Fossil SCM

⌈⌋ ⎇ branch:  Fossil Skins Extra


Check-in [ab76ada131]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Update comments on user.* table.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: ab76ada131f9c280af44ce2213b5edb50d01e764
User & Date: mario 2021-04-06 11:52:30
Context
2021-04-06
18:22
Rough outline of how to map requests onto fossil commands. check-in: 32f4421035 user: mario tags: trunk
11:52
Update comments on user.* table. check-in: ab76ada131 user: mario tags: trunk
2021-04-05
18:44
fix PMD extraction regex check-in: f6b70114ca user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to extroot/auth.

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
}

#-- database (== fossil repo)
function db($sql="", $params=[]) {
    static $db;
    if (empty($db)) {
        $db = new PDO("sqlite:$_SERVER[FOSSIL_REPOSITORY]");
        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
    }
    if ($params) {
        $stmt = $db->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll();
    }
    else {
        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,grant
        `scope` TEXT,           -- permissions (create,update,delete)
        `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







|













|







79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
}

#-- database (== fossil repo)
function db($sql="", $params=[]) {
    static $db;
    if (empty($db)) {
        $db = new PDO("sqlite:$_SERVER[FOSSIL_REPOSITORY]");
        #$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
    }
    if ($params) {
        $stmt = $db->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll();
    }
    else {
        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,           -- permissions (create,update,delete)
        `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
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
163
164
165
166
167
168
169
    print($text);
}
function h($s) {
    return htmlspecialchars($s, ENT_QUOTES|ENT_HTML5, "UTF-8");
}


#-- test if http://identity is whitelisted in `me` column for user
function allowed_identity($user, $url) {
    $all = db("SELECT * FROM user WHERE login=?", [$user]);
    preg_match_all("~\\b https?://(\S+) (?<![,;|<>'\"])~x", join(", ", $all[0]), $uu);  # search all fields for urls
    foreach ($uu[1] as $item) {
        if (trim_url($item) == trim_url($url)) {
             return True;
        }
    }
}
function trim_url($url) {


    return strtolower(preg_replace("~^https?://|/+$~", "", $url));
}
function base64_urlencode($raw) {
    return strtr(trim(base64_encode($raw), "="), "+/", "-_");
}

#-- load authorization properties by auth code
function get_token_by_code($code) {
    return db("SELECT * FROM fx_auth WHERE code=?", [$code]) ?: [[]];
}
function clean_expired_token() {
    db("DELETE FROM fx_auth WHERE expires < ?", [time()]);
}


#-- input fields for "scope" token request
function scope_list($html="") {
    $scopes = ["create"=>"checked", "update"=>"checked", "delete"=>"", "ticket"=>"checked", "wiki"=>"", "forum"=>""];
    if (!empty($_REQUEST["scope"]) and preg_match_all("/(\w+)/", $_REQUEST["scope"], $uu)) {
        foreach ($uu[1] as $field) { $scopes[$field] = "checked"; }
    }
    foreach ($scopes as $field=>$checked) {
        $html .= "<li style='flex: 1 0 33%; list-style-type: none;'> <label><input type=checkbox $checked name='scope[]' value=$field> $field</label>\n";







|










>
>
|














|







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
163
164
165
166
167
168
169
170
171
    print($text);
}
function h($s) {
    return htmlspecialchars($s, ENT_QUOTES|ENT_HTML5, "UTF-8");
}


#-- test if http://identity/ is whitelisted in user.`homepage`/`info` column
function allowed_identity($user, $url) {
    $all = db("SELECT * FROM user WHERE login=?", [$user]);
    preg_match_all("~\\b https?://(\S+) (?<![,;|<>'\"])~x", join(", ", $all[0]), $uu);  # search all fields for urls
    foreach ($uu[1] as $item) {
        if (trim_url($item) == trim_url($url)) {
             return True;
        }
    }
}
function trim_url($url) {
    # Very crude URL equality test.
    # Strip any http:// prefix, trailing slashes, or /home, /index (when referring to fossil instance).
    return strtolower(preg_replace("~ ^https?:// | /+(home|index)$ | /+$ ~x", "", $url));
}
function base64_urlencode($raw) {
    return strtr(trim(base64_encode($raw), "="), "+/", "-_");
}

#-- load authorization properties by auth code
function get_token_by_code($code) {
    return db("SELECT * FROM fx_auth WHERE code=?", [$code]) ?: [[]];
}
function clean_expired_token() {
    db("DELETE FROM fx_auth WHERE expires < ?", [time()]);
}


#-- <form> checkboxes for "scope" token request, extended default list and ?scope=… requests
function scope_list($html="") {
    $scopes = ["create"=>"checked", "update"=>"checked", "delete"=>"", "ticket"=>"checked", "wiki"=>"", "forum"=>""];
    if (!empty($_REQUEST["scope"]) and preg_match_all("/(\w+)/", $_REQUEST["scope"], $uu)) {
        foreach ($uu[1] as $field) { $scopes[$field] = "checked"; }
    }
    foreach ($scopes as $field=>$checked) {
        $html .= "<li style='flex: 1 0 33%; list-style-type: none;'> <label><input type=checkbox $checked name='scope[]' value=$field> $field</label>\n";
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
    $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)) {
        return page_html("<h2>Invalid identity</h2> User doesn't have '{$h($me)}' reserved in fx_auth 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`, `login`, `me`, `client_id`, `redirect_uri`, `state`, `code_challenge`, `code_challenge_m`, `expires`)







|







186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
    $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)) {
        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`, `login`, `me`, `client_id`, `redirect_uri`, `state`, `code_challenge`, `code_challenge_m`, `expires`)
266
267
268
269
270
271
272

273
274
275
276
277
278
279
    elseif (($token["client_id"] != $client_id) or ($token["redirect_uri"] != $redirect_uri)) {
        die(json_encode(["error" =>  "invalid_scope", "error_description" => "code does not match previous params (client_id, redirect_uri)"]));
    }
    elseif ($code_challenge and base64_urlencode(hash("sha256", $code_verifier, 1)) != $code_challenge) {
        die(json_encode(["error" =>  "unauthorized_client", "error_description" => "code_challenge does not match code_verifier"]));
    }
    else {

        die(json_encode(["me" => $token["me"], "scope" => $token["scope"]]));
    }
}


#-- run
if (empty($_POST["redirect_target"]) and !empty($_REQUEST["code"])) {  # ?code=… when the remote app verifies the response







>







268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
    elseif (($token["client_id"] != $client_id) or ($token["redirect_uri"] != $redirect_uri)) {
        die(json_encode(["error" =>  "invalid_scope", "error_description" => "code does not match previous params (client_id, redirect_uri)"]));
    }
    elseif ($code_challenge and base64_urlencode(hash("sha256", $code_verifier, 1)) != $code_challenge) {
        die(json_encode(["error" =>  "unauthorized_client", "error_description" => "code_challenge does not match code_verifier"]));
    }
    else {
        # approved
        die(json_encode(["me" => $token["me"], "scope" => $token["scope"]]));
    }
}


#-- run
if (empty($_POST["redirect_target"]) and !empty($_REQUEST["code"])) {  # ?code=… when the remote app verifies the response