Fossil SCM

fossil-scm / src / robot.c
Blame History Raw 739 lines
1
/*
2
** Copyright (c) 2025 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code that attempts to prevent robots and
19
** especially bot-nets from consume excess CPU and bandwidth when
20
** Fossil is run as a service.
21
*/
22
#include "config.h"
23
#include "robot.h"
24
#include <assert.h>
25
#include <time.h>
26
27
/*
28
** The name of the cookie used to demonstrate that the client has been
29
** tested and is believed to be operated by a human, not by a robot.
30
*/
31
#if INTERFACE
32
#define ROBOT_COOKIE "fossil-client-ok"
33
#endif
34
35
/*
36
** Values computed only once and then cached.
37
*/
38
static struct RobotCache {
39
unsigned int h1, h2; /* Proof-of-work hash values */
40
unsigned int resultCache; /* 0: unknown. 1: human 2: might-be-robot */
41
} robot = { 0, 0, 0 };
42
43
/*
44
** Allowed values for robot.resultCache.
45
**
46
** The names are slightly misleading. KNOWN_NOT_ROBOT might be set even
47
** if the client is a robot, but only if the robot is an approved robot.
48
** A better name might be "KNOWN_NOT_UNAUTHORIZED_ROBOT", but that is too
49
** long of a name.
50
*/
51
#define KNOWN_NOT_ROBOT 1 /* Approved to consume CPU and bandwidth */
52
#define MIGHT_BE_ROBOT 2 /* Might be an unapproved robot */
53
54
/*
55
** Compute two hashes, robot.h1 and robot.h2, that are used as
56
** part of determining whether or not the HTTP client is a robot.
57
** These hashes are based on current time, client IP address,
58
** and User-Agent. robot.h1 is for the current time slot and
59
** robot.h2 is the previous.
60
**
61
** The hashes are integer values between 100,000,000 and 999,999,999
62
** inclusive.
63
*/
64
static void robot_pow_hash(void){
65
const char *az[2], *z;
66
sqlite3_int64 tm;
67
unsigned int h1, h2, k;
68
69
if( robot.h1 ) return; /* Already computed */
70
71
/* Construct a proof-of-work value based on the IP address of the
72
** sender and the sender's user-agent string. The current time also
73
** affects the pow value, so actually compute two values, one for the
74
** current 900-second interval and one for the previous. Either can
75
** match. The pow-value is an integer between 100,000,000 and
76
** 999,999,999.
77
*/
78
az[0] = P("REMOTE_ADDR");
79
az[1] = P("HTTP_USER_AGENT");
80
tm = time(0);
81
h1 = (unsigned)(tm/900)&0xffffffff;
82
h2 = h1 - 1;
83
for(k=0; k<2; k++){
84
z = az[k];
85
if( z==0 ) continue;
86
while( *z ){
87
h1 = (h1 + *(unsigned char*)z)*0x9e3779b1;
88
h2 = (h2 + *(unsigned char*)z)*0x9e3779b1;
89
z++;
90
}
91
}
92
robot.h1 = (h1 % 900000000) + 100000000;
93
robot.h2 = (h2 % 900000000) + 100000000;
94
}
95
96
/*
97
** Remember that the client is human, as evidenced by the presence of
98
** the fossil-client-ok or similar proof of work. Enable hyperlinks if
99
** auto-hyperlink is turned on.
100
*/
101
static void remember_is_human(void){
102
robot.resultCache = KNOWN_NOT_ROBOT;
103
if( g.perm.Hyperlink==0 ){
104
int autoLink = db_get_int("auto-hyperlink",1);
105
if( autoLink==1 ){
106
g.jsHref = 1;
107
g.perm.Hyperlink = 1;
108
}else if( autoLink==2 ){
109
g.perm.Hyperlink = 1;
110
}
111
}
112
}
113
114
/*
115
** Return true if the HTTP client has not demonstrated that it is
116
** human interactive. Return false is the HTTP client might be
117
** a non-interactive robot.
118
**
119
** For this routine, any of the following is considered proof that
120
** the HTTP client is not a robot:
121
**
122
** 1. There is a valid login, including "anonymous". User "nobody"
123
** is not a valid login, but every other user is.
124
**
125
** 2. There exists a ROBOT_COOKIE with the correct proof-of-work
126
** value.
127
**
128
** 3. There exists a proof=VALUE query parameter where VALUE is
129
** a correct proof-of-work value.
130
**
131
** 4. There exists a valid token=VALUE query parameter.
132
**
133
** After being run once, this routine caches its findings and
134
** returns very quickly on subsequent invocations.
135
*/
136
int client_might_be_a_robot(void){
137
const char *z;
138
139
/* Only do this computation once, then cache the results for future
140
** use */
141
if( robot.resultCache ){
142
return robot.resultCache==MIGHT_BE_ROBOT;
143
}
144
145
/* Condition 1: Is there a valid login?
146
*/
147
if( g.userUid==0 ){
148
login_check_credentials();
149
}
150
if( g.zLogin!=0 ){
151
robot.resultCache = KNOWN_NOT_ROBOT;
152
return 0;
153
}
154
155
/* Condition 2: If there is already a proof-of-work cookie
156
** with a correct value, then the user agent has been authenticated.
157
*/
158
z = P(ROBOT_COOKIE);
159
if( z ){
160
unsigned h = atoi(z);
161
robot_pow_hash();
162
if( (h==robot.h1 || h==robot.h2) && !cgi_is_qp(ROBOT_COOKIE) ){
163
remember_is_human();
164
return 0;
165
}
166
}
167
168
/* Condition 3: There is a "proof=VALUE" query parameter with a valid
169
** VALUE attached. If this is the case, also set the robot cookie
170
** so that future requests will hit condition 2 above.
171
*/
172
z = P("proof");
173
if( z ){
174
unsigned h = atoi(z);
175
robot_pow_hash();
176
if( h==robot.h1 || h==robot.h2 ){
177
cgi_set_cookie(ROBOT_COOKIE,z,"/",900);
178
remember_is_human();
179
return 0;
180
}
181
cgi_tag_query_parameter("proof");
182
}
183
184
/* Condition 4: If there is a "token=VALUE" query parameter with a
185
** valid VALUE argument, then assume that the request is coming from
186
** either an interactive human session, or an authorized robot that we
187
** want to treat as human. Allow it through and also set the robot cookie.
188
*/
189
z = P("token");
190
if( z!=0 ){
191
if( db_exists("SELECT 1 FROM config"
192
" WHERE name='token-%q'"
193
" AND json_valid(value,6)"
194
" AND value->>'user' IS NOT NULL", z)
195
){
196
char *zVal;
197
robot_pow_hash();
198
zVal = mprintf("%u", robot.h1);
199
cgi_set_cookie(ROBOT_COOKIE,zVal,"/",900);
200
fossil_free(zVal);
201
robot.resultCache = KNOWN_NOT_ROBOT;
202
return 0; /* There is a valid token= query parameter */
203
}
204
cgi_tag_query_parameter("token");
205
}
206
207
/* We have no proof that the request is coming from an interactive
208
** human session, so assume the request comes from a robot.
209
*/
210
robot.resultCache = MIGHT_BE_ROBOT;
211
return 1;
212
}
213
214
/*
215
** Rewrite the current page with content that attempts
216
** to prove that the client is not a robot.
217
*/
218
static void ask_for_proof_that_client_is_not_robot(void){
219
unsigned p1, p2, p3, p4, p5, k2, k3;
220
int k;
221
222
/* Ask the client to present proof-of-work */
223
cgi_reset_content();
224
cgi_set_content_type("text/html");
225
style_header("Browser Verification");
226
@ <h1 id="x1">Checking to see if you are a robot<span id="x2"></span></h1>
227
@ <form method="GET" id="x6"><p>
228
@ <span id="x3" style="visibility:hidden;">\
229
@ Press <input type="submit" id="x5" value="Ok" focus> to continue</span>
230
@ <span id="x7" style="visibility:hidden;">You appear to be a robot.</span>\
231
@ </p>
232
if( g.zExtra && g.zExtra[0] ) cgi_tag_query_parameter("name");
233
cgi_query_parameters_to_hidden();
234
@ <input id="x4" type="hidden" name="proof" value="0">
235
@ </form>
236
@ <script nonce='%s(style_nonce())'>
237
@ function aaa(x){return document.getElementById(x);}\
238
@ function bbb(h,a){\
239
@ aaa("x4").value=h;\
240
@ if((a%%75)==0){\
241
@ aaa("x2").textContent=aaa("x2").textContent+".";\
242
@ }var z;\
243
@ if(a>0){\
244
@ setTimeout(bbb,1,h+a,a-1);\
245
@ }else if((z=window.getComputedStyle(document.body).zIndex)==='0'||z===0){\
246
@ aaa("x3").style.visibility="visible";\
247
@ aaa("x2").textContent="";\
248
@ aaa("x1").textContent="All clear";\
249
@ aaa("x6").onsubmit=function(){aaa("x3").style.visibility="hidden";};\
250
@ aaa("x5").focus();\
251
@ }else{\
252
@ aaa("x7").style.visibility="visible";\
253
@ aaa("x2").textContent="";\
254
@ aaa("x3").style.display="none";\
255
@ aaa("x1").textContent="Access Denied";\
256
@ }\
257
@ }\
258
robot_pow_hash();
259
k = 400 + robot.h2%299;
260
k2 = (robot.h2/299)%99 + 973;
261
k3 = (robot.h2/(299*99))%99 + 811;
262
p1 = (k*k + k)/2;
263
p2 = robot.h1-p1;
264
p3 = p2%k2;
265
p4 = (p2/k2)%k3;
266
p5 = p2/(k2*k3);
267
@ function ccc(a,b,c){return (a*%u(k3)+b)*%u(k2)+c;}\
268
@ window.addEventListener('load',function(){\
269
@ bbb(ccc(%u(p5),%u(p4),%u(p3)),%u(k));},false);
270
/* Prevent successfully completed robot checks from reappearing and force
271
** incomplete checks to start over when navigating back and forward. More
272
** information: <https://stackoverflow.com/a/43043658>. */
273
@ window.addEventListener('pageshow',function(e){if(e.persisted)\
274
@ window.location.reload();});
275
@ </script>
276
style_finish_page();
277
}
278
279
/*
280
** SETTING: robot-restrict width=40 block-text
281
** The VALUE of this setting is a list of GLOB patterns that match
282
** pages for which complex HTTP requests from unauthenticated clients
283
** should be disallowed. "Unauthenticated" means the user is "nobody".
284
** The recommended value for this setting is:
285
**
286
** timelineX,diff,annotate,fileage,file,finfo,reports,tree,hexdump,download
287
**
288
** Usually the tag should exactly match the page name. The "diff" tag
289
** covers all diffing pages such as /vdiff, /fdiff, and /vpatch. The
290
** "annotate" tag also covers /blame and /praise. "zip" also covers
291
** /tarball and /sqlar. If a tag has an "X" character appended then it
292
** only applies if query parameters are such that the page is particularly
293
** difficult to compute. Useful "X" tags include "timelineX" and "zipX".
294
** The "ext" tag matches all extension, but a tag of the form "ext/PATH"
295
** only matches the extension at PATH.
296
**
297
** See the [[robot-zip-leaf]] and [[robot-zip-tag]] settings
298
** for additional controls associated with the "zipX" restriction.
299
**
300
** Change this setting "off" to disable all robot restrictions.
301
*/
302
/*
303
** SETTING: robot-exception width=40 block-text
304
**
305
** The value of this setting should be a regular expression.
306
** If it matches the REQUEST_URI without the SCRIPT_NAME prefix
307
** matches this regular expression, then the request is an exception
308
** to anti-robot defenses and should be allowed through. For
309
** example, to allow robots to download tarballs or ZIP archives
310
** for named versions and releases, you could use an expression like
311
** this:
312
**
313
** ^/tarball/(version-[0-9.]+|release)/
314
**
315
** This setting can hold multiple regular expressions, one
316
** regular expression per line. The input URL is exempted from
317
** anti-robot defenses if any of the multiple regular expressions
318
** matches.
319
*/
320
/*
321
** SETTING: robot-zip-leaf boolean
322
**
323
** If this setting is true, the robots are allowed to download tarballs,
324
** ZIP-archives, and SQL-archives even though "zipX" is found in
325
** the [[robot-restrict]] setting as long as the specific check-in being
326
** downloaded is a leaf check-in.
327
*/
328
/*
329
** SETTING: robot-zip-tag width=40 block-text
330
**
331
** If this setting is a list of GLOB patterns matching tags,
332
** then robots are allowed to download tarballs, ZIP-archives, and
333
** SQL-archives even though "zipX" appears in [[robot-restrict]], as long as
334
** the specific check-in being downloaded has a tags that matches
335
** the GLOB list of this setting. Recommended value:
336
** "release,robot-access".
337
*/
338
339
/*
340
** Return the default restriction GLOB
341
*/
342
const char *robot_restrict_default(void){
343
/* NOTE: The default value is also mentioned in the online help screen of
344
** the "robot-restrict" setting, and in the www/antibot.wiki document. */
345
return "timelineX,diff,annotate,fileage,file,finfo,reports,"
346
"tree,hexdump,download";
347
}
348
349
/*
350
** Return true if zTag matches one of the tags in the robot-restrict
351
** setting.
352
**
353
** A zTag of "*" matches anything.
354
*/
355
static int robot_restrict_has_tag(const char *zTag){
356
static const char *zGlob = 0;
357
if( zGlob==0 ){
358
zGlob = db_get("robot-restrict",robot_restrict_default());
359
if( zGlob==0 ) zGlob = "";
360
}
361
if( zGlob[0]==0 || fossil_strcmp(zGlob, "off")==0 ){
362
return 0;
363
}
364
if( zTag==0 || (zTag[0]=='*' && zTag[1]==0) ){
365
return 1;
366
}
367
return glob_multi_match(zGlob,zTag);
368
}
369
370
/*
371
** Check the request URI to see if it matches one of the URI
372
** exceptions listed in the robot-exception setting. Return true
373
** if it does. Return false if it does not.
374
**
375
** For the purposes of this routine, the "request URI" means
376
** the REQUEST_URI value with the SCRIPT_NAME prefix removed and
377
** with QUERY_STRING appended with a "?" separator if QUERY_STRING
378
** is not empty.
379
**
380
** If the robot-exception setting does not exist or is an empty
381
** string, then return false.
382
*/
383
int robot_exception(void){
384
const char *zRE = db_get("robot-exception",0);
385
const char *zQS; /* QUERY_STRING */
386
const char *zURI; /* REQUEST_URI */
387
const char *zSN; /* SCRIPT_NAME */
388
const char *zNL; /* Next newline character */
389
char *zRequest; /* REQUEST_URL w/o SCRIPT_NAME prefix + QUERY_STRING */
390
int nRequest; /* Length of zRequest in bytes */
391
size_t nURI, nSN; /* Length of zURI and zSN */
392
int bMatch = 0; /* True if there is a match */
393
394
if( zRE==0 ) return 0;
395
if( zRE[0]==0 ) return 0;
396
zURI = PD("REQUEST_URI","");
397
nURI = strlen(zURI);
398
zSN = PD("SCRIPT_NAME","");
399
nSN = strlen(zSN);
400
if( nSN<=nURI ) zURI += nSN;
401
zQS = P("QUERY_STRING");
402
if( zQS && zQS[0] ){
403
zRequest = mprintf("%s?%s", zURI, zQS);
404
}else{
405
zRequest = fossil_strdup(zURI);
406
}
407
nRequest = (int)strlen(zRequest);
408
while( zRE[0] && bMatch==0 ){
409
char *z;
410
const char *zErr;
411
size_t n;
412
ReCompiled *pRe;
413
zNL = strchr(zRE,'\n');
414
if( zNL ){
415
n = (size_t)(zNL - zRE)+1;
416
while( zNL>zRE && fossil_isspace(zNL[0]) ) zNL--;
417
if( zNL==zRE ){
418
zRE += n;
419
continue;
420
}
421
}else{
422
n = strlen(zRE);
423
}
424
z = mprintf("%.*s", (int)(zNL - zRE)+1, zRE);
425
zRE += n;
426
zErr = fossil_re_compile(&pRe, z, 0);
427
if( zErr ){
428
fossil_warning("robot-exception error \"%s\" in expression \"%s\"\n",
429
zErr, z);
430
fossil_free(z);
431
continue;
432
}
433
fossil_free(z);
434
bMatch = re_match(pRe, (const unsigned char*)zRequest, nRequest);
435
re_free(pRe);
436
}
437
fossil_free(zRequest);
438
return bMatch;
439
}
440
441
/*
442
** Return true if one or more of the conditions below are true.
443
** Return false if all of the following are false:
444
**
445
** * The zTag is on the robot-restrict list and there exists
446
** some tags that do not match robot-restrict or else zTag is
447
** neither "/" or "xfer" or "login".
448
**
449
** The complication of the previous rule (everything after the
450
** first "and") prevents a robot-restrict setting that contains
451
** "*" from trying to bring up a robot-captcha for a "sync"
452
** operation (the "/" or "xfer" page) or for the "login" page.
453
**
454
** * The client that submitted the HTTP request might be
455
** a robot
456
**
457
** * The Request URI does not match any of the exceptions
458
** in the robot-exception setting.
459
**
460
** In other words, return true if a call to robot_restrict() would
461
** return true and false if a call to robot_restrict() would return
462
** false.
463
**
464
** The difference between this routine an robot_restrict() is that
465
** this routine does not generate a proof-of-work captcha. This
466
** routine does not change the HTTP reply in any way. It simply
467
** returns true or false.
468
*/
469
int robot_would_be_restricted(const char *zTag){
470
if( robot.resultCache==KNOWN_NOT_ROBOT ) return 0;
471
if( !robot_restrict_has_tag(zTag) ) return 0;
472
if( !client_might_be_a_robot() ) return 0;
473
if( robot_exception() ){
474
robot.resultCache = KNOWN_NOT_ROBOT;
475
return 0;
476
}
477
if( robot_restrict_has_tag("-not-a-real-tag-")
478
&& (strcmp(zTag,"login")==0 || strcmp(zTag,"xfer")==0 || zTag[0]=='/')
479
){
480
return 0;
481
}
482
return 1;
483
}
484
485
/*
486
** Check to see if the page named in the argument is on the
487
** robot-restrict list. If it is on the list and if the user
488
** is might be a robot, then bring up a captcha to test to make
489
** sure that client is not a robot.
490
**
491
** This routine returns true if a captcha was rendered and if subsequent
492
** page generation should be aborted. It returns false if the page
493
** should not be restricted and should be rendered normally.
494
*/
495
int robot_restrict(const char *zTag){
496
if( robot_would_be_restricted(zTag) ){
497
/* Generate the proof-of-work captcha */
498
ask_for_proof_that_client_is_not_robot();
499
return 1;
500
}else{
501
return 0;
502
}
503
}
504
505
/*
506
** Check to see if a robot is allowed to download a tarball, ZIP archive,
507
** or SQL Archive for a particular check-in identified by the "rid"
508
** argument. Return true to block the download. Return false to
509
** continue. Prior to returning true, a captcha is presented to the user.
510
** No output is generated when returning false.
511
**
512
** The rules:
513
**
514
** (1) If "zipX" is missing from the robot-restrict setting, then robots
515
** are allowed to download any archive. None of the remaining rules
516
** below are consulted unless "zipX" is on the robot-restrict setting.
517
**
518
** (2) If the robot-zip-leaf setting is true, then robots are allowed
519
** to download archives for any leaf check-in. This allows URL like
520
** /tarball/trunk/archive.tar.gz to work since branch labels like "trunk"
521
** always resolve to a leaf.
522
**
523
** (3) If the robot-zip-tag setting is a comma-separated tags, then any
524
** check-in that contains one of the tags on that list is allowed to
525
** be downloaded. This allows check-ins with tags like "release" or
526
** "robot-access" to be downloaded by robots.
527
*/
528
int robot_restrict_zip(int rid){
529
const char *zTag;
530
if( !robot_restrict_has_tag("zipX") || !client_might_be_a_robot() ){
531
return 0; /* Rule (1) */
532
}
533
534
if( db_get_boolean("robot-zip-leaf",0) && is_a_leaf(rid) ){
535
return 0; /* Rule (2) */
536
}
537
538
zTag = db_get("robot-zip-tag",0);
539
if( zTag && zTag[0] && fossil_strcmp(zTag,"off")!=0 ){
540
int ok = 0;
541
Stmt q;
542
db_prepare(&q,
543
"SELECT substr(tagname,5) FROM tagxref, tag"
544
" WHERE tagxref.rid=%d"
545
" AND tag.tagid=tagxref.tagid"
546
" AND tagxref.tagtype=1"
547
" AND tag.tagname GLOB 'sym-*'",
548
rid
549
);
550
while( !ok && db_step(&q)==SQLITE_ROW ){
551
if( glob_multi_match(zTag, db_column_text(&q,0)) ) ok = 1;
552
}
553
db_finalize(&q);
554
if( ok ) return 0; /* Rule (3) */
555
}
556
557
/* Generate the proof-of-work captcha */
558
ask_for_proof_that_client_is_not_robot();
559
return 1;
560
}
561
562
/*
563
** WEBPAGE: test-robotck
564
**
565
** Run the robot_restrict() function using the value of the "name="
566
** query parameter as an argument. Used for testing the robot_restrict()
567
** logic.
568
**
569
** Whenever this page is successfully rendered (when it doesn't go to
570
** the captcha) it deletes the proof-of-work cookie. So reloading the
571
** page will reset the cookie and restart the verification.
572
**
573
** If the zip=CHECKIN query parameter is provided, then also invoke
574
** robot_restrict_archive() on the RID of CHECKIN.
575
*/
576
void robot_restrict_test_page(void){
577
const char *zName = P("name");
578
const char *zZip = P("zip");
579
const char *zP1 = P("proof");
580
const char *zP2 = P(ROBOT_COOKIE);
581
const char *z;
582
int rid = 0;
583
if( zName==0 || zName[0]==0 ) zName = g.zPath;
584
login_check_credentials();
585
if( g.zLogin==0 ){ login_needed(1); return; }
586
g.zLogin = 0;
587
if( robot_restrict(zName) ) return;
588
if( zZip && zZip[0] ){
589
rid = symbolic_name_to_rid(zZip, "ci");
590
if( rid && robot_restrict_zip(rid) ) return;
591
}
592
style_set_current_feature("test");
593
style_header("robot_restrict() test");
594
@ <h1>Captcha passed</h1>
595
@
596
@ <p>
597
if( zP1 && zP1[0] ){
598
@ proof=%h(zP1)<br>
599
}
600
if( zP2 && zP2[0] ){
601
@ %h(ROBOT_COOKIE)=%h(zP2)<br>
602
cgi_set_cookie(ROBOT_COOKIE,"",0,-1);
603
}
604
if( zZip && zZip[0] ){
605
@ zip=%h(zZip)<br>
606
@ rid=%d(rid)<br>
607
}
608
if( g.perm.Admin ){
609
z = db_get("robot-restrict",robot_restrict_default());
610
if( z && z[0] ){
611
@ robot-restrict=%h(z)</br>
612
}
613
@ robot.h1=%u(robot.h1)<br>
614
@ robot.h2=%u(robot.h2)<br>
615
switch( robot.resultCache ){
616
case MIGHT_BE_ROBOT: {
617
@ robot.resultCache=MIGHT_BE_ROBOT<br>
618
break;
619
}
620
case KNOWN_NOT_ROBOT: {
621
@ robot.resultCache=KNOWN_NOT_ROBOT<br>
622
break;
623
}
624
default: {
625
@ robot.resultCache=OTHER (%d(robot.resultCache))<br>
626
break;
627
}
628
}
629
}
630
@ </p>
631
@ <p><a href="%R/test-robotck/%h(zName)">Retry</a>
632
style_finish_page();
633
}
634
635
/*
636
** WEBPAGE: tokens
637
**
638
** Allow users to create, delete, and view their access token.
639
**
640
** The access token is a string TOKEN which if included in a query
641
** parameter like "token=TOKEN" authenticates a request as coming
642
** from an authorized agent. This can be used, for example, by
643
** script to access content without running into problems with
644
** robot defenses.
645
*/
646
void tokens_page(void){
647
char *zMyToken;
648
649
login_check_credentials();
650
style_set_current_feature("tokens");
651
style_header("Access Tokens");
652
if( g.zLogin==0 || fossil_strcmp(g.zLogin,"anonymous")==0 ){
653
@ User "%h(g.zLogin?g.zLogin:"anonymous")" is not allowed to
654
@ own or use access tokens.
655
style_finish_page();
656
return;
657
}
658
if( g.perm.Admin && P("del")!=0 ){
659
const char *zDel = P("del");
660
db_unprotect(PROTECT_CONFIG);
661
db_multi_exec(
662
"DELETE FROM config WHERE name='token-%q'",
663
zDel);
664
db_protect_pop();
665
}
666
zMyToken = db_text(0,
667
"SELECT substr(name,7) FROM config"
668
" WHERE name GLOB 'token-*'"
669
" AND json_valid(value,6)"
670
" AND value->>'user' = %Q",
671
g.zLogin
672
);
673
if( zMyToken==0 && P("new") ){
674
sqlite3_uint64 r;
675
sqlite3_randomness(sizeof(r),&r);
676
zMyToken = mprintf("%016llx", r);
677
db_unprotect(PROTECT_CONFIG);
678
db_multi_exec(
679
"INSERT INTO config(name,value,mtime)"
680
"VALUES('token-%q','{user:%!j}',now())",
681
zMyToken, g.zLogin
682
);
683
db_protect_pop();
684
}else if( zMyToken!=0 && P("selfdel")
685
&& fossil_strcmp(zMyToken,P("selfdel"))==0 ){
686
db_unprotect(PROTECT_CONFIG);
687
db_multi_exec(
688
"DELETE FROM config WHERE name='token-%q'",
689
zMyToken);
690
db_protect_pop();
691
zMyToken = 0;
692
}
693
if( zMyToken==0 ){
694
@ <p>You do not currently have an access token.
695
@ <a href="%R/tokens?new=true">Create one</a>
696
}else{
697
@ <p>Your access token is "%h(zMyToken)".
698
@ <p>Use this token as the value of the token= query parameter
699
@ to bypass robot defenses on unauthenticated queries to this
700
@ server (%R). Do not misuse your token. Keep it confidential.
701
@ If you misuse your token, or if somebody else steals your token
702
@ and misuses, that can result in loss of access privileges to this
703
@ server.
704
@ <p><a href="%R/tokens?selfdel=%h(zMyToken)">Delete my token</a>
705
}
706
if( g.perm.Admin ){
707
int nTok = 0;
708
Stmt s;
709
db_prepare(&s,
710
"SELECT substr(name,7), value->>'user', datetime(mtime,'unixepoch')"
711
" FROM config"
712
" WHERE name GLOB 'token-*'"
713
" AND json_valid(value,6)"
714
);
715
while( db_step(&s)==SQLITE_ROW ){
716
if( nTok==0 ){
717
@ <hr>
718
@ <p>All tokens</p>
719
@ <table border="1" cellpadding="5" cellspacing="0">
720
@ <tr><th>User <th>Token <th>Date <th> &nbsp;</tr>
721
}
722
nTok++;
723
@ <tr><td>%h(db_column_text(&s,1))
724
@ <td>%h(db_column_text(&s,0))
725
@ <td>%h(db_column_text(&s,2))
726
@ <td><a href="%R/tokens?del=%h(db_column_text(&s,0))">delete</a>
727
@ </tr>
728
}
729
db_finalize(&s);
730
if( nTok==0 ){
731
@ <hr>
732
@ <p>There are access tokens defined for this repository.
733
}else{
734
@ </table>
735
}
736
}
737
style_finish_page();
738
}
739

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button