|
1
|
#ifdef FOSSIL_ENABLE_JSON |
|
2
|
/* |
|
3
|
** Copyright (c) 2011 D. Richard Hipp |
|
4
|
** |
|
5
|
** This program is free software; you can redistribute it and/or |
|
6
|
** modify it under the terms of the Simplified BSD License (also |
|
7
|
** known as the "2-Clause License" or "FreeBSD License".) |
|
8
|
** |
|
9
|
** This program is distributed in the hope that it will be useful, |
|
10
|
** but without any warranty; without even the implied warranty of |
|
11
|
** merchantability or fitness for a particular purpose. |
|
12
|
** |
|
13
|
** Author contact information: |
|
14
|
** [email protected] |
|
15
|
** http://www.hwaci.com/drh/ |
|
16
|
** |
|
17
|
*/ |
|
18
|
|
|
19
|
#include "config.h" |
|
20
|
#include "json_login.h" |
|
21
|
|
|
22
|
#if INTERFACE |
|
23
|
#include "json_detail.h" |
|
24
|
#endif |
|
25
|
|
|
26
|
|
|
27
|
/* |
|
28
|
** Implementation of the /json/login page. |
|
29
|
** |
|
30
|
*/ |
|
31
|
cson_value * json_page_login(void){ |
|
32
|
char preciseErrors = /* if true, "complete" JSON error codes are used, |
|
33
|
else they are "dumbed down" to a generic login |
|
34
|
error code. |
|
35
|
*/ |
|
36
|
#if 1 |
|
37
|
g.json.errorDetailParanoia ? 0 : 1 |
|
38
|
#else |
|
39
|
0 |
|
40
|
#endif |
|
41
|
; |
|
42
|
/* |
|
43
|
FIXME: we want to check the GET/POST args in this order: |
|
44
|
|
|
45
|
- GET: name, n, password, p |
|
46
|
- POST: name, password |
|
47
|
|
|
48
|
but fossil's age-old behaviour of treating the last element of |
|
49
|
PATH_INFO as the value for the name parameter breaks that. |
|
50
|
|
|
51
|
Summary: If we check for P("name") first, then P("n"), then ONLY a |
|
52
|
GET param of "name" will match ("n" is not recognized). If we |
|
53
|
reverse the order of the checks then both forms work. The |
|
54
|
"p"/"password" check is not affected by this. |
|
55
|
*/ |
|
56
|
char const * name = cson_value_get_cstr(json_req_payload_get("name")); |
|
57
|
char const * pw = NULL; |
|
58
|
char const * anonSeed = NULL; |
|
59
|
cson_value * payload = NULL; |
|
60
|
int uid = 0; |
|
61
|
/* reminder to self: Fossil internally (for the sake of /wiki) |
|
62
|
interprets paths in the form /foo/bar/baz such that P("name") == |
|
63
|
"bar/baz". This collides with our name/password checking, and |
|
64
|
thus we do some rather elaborate name=... checking. |
|
65
|
*/ |
|
66
|
pw = cson_value_get_cstr(json_req_payload_get("password")); |
|
67
|
if( !pw ){ |
|
68
|
pw = PD("p",NULL); |
|
69
|
if( !pw ){ |
|
70
|
pw = PD("password",NULL); |
|
71
|
} |
|
72
|
} |
|
73
|
if(!pw){ |
|
74
|
g.json.resultCode = preciseErrors |
|
75
|
? FSL_JSON_E_LOGIN_FAILED_NOPW |
|
76
|
: FSL_JSON_E_LOGIN_FAILED; |
|
77
|
return NULL; |
|
78
|
} |
|
79
|
|
|
80
|
if( !name ){ |
|
81
|
name = PD("n",NULL); |
|
82
|
if( !name ){ |
|
83
|
name = PD("name",NULL); |
|
84
|
if( !name ){ |
|
85
|
g.json.resultCode = preciseErrors |
|
86
|
? FSL_JSON_E_LOGIN_FAILED_NONAME |
|
87
|
: FSL_JSON_E_LOGIN_FAILED; |
|
88
|
return NULL; |
|
89
|
} |
|
90
|
} |
|
91
|
} |
|
92
|
|
|
93
|
if(0 == strcmp("anonymous",name)){ |
|
94
|
/* check captcha/seed values... */ |
|
95
|
enum { SeedBufLen = 100 /* in some JSON tests i once actually got an |
|
96
|
80-digit number. |
|
97
|
*/ |
|
98
|
}; |
|
99
|
static char seedBuffer[SeedBufLen]; |
|
100
|
cson_value const * jseed = json_getenv(FossilJsonKeys.anonymousSeed); |
|
101
|
seedBuffer[0] = 0; |
|
102
|
if( !jseed ){ |
|
103
|
jseed = json_req_payload_get(FossilJsonKeys.anonymousSeed); |
|
104
|
if( !jseed ){ |
|
105
|
jseed = json_getenv("cs") /* name used by HTML interface */; |
|
106
|
} |
|
107
|
} |
|
108
|
if(jseed){ |
|
109
|
if( cson_value_is_number(jseed) ){ |
|
110
|
sqlite3_snprintf((int)SeedBufLen, seedBuffer, "%"CSON_INT_T_PFMT, |
|
111
|
cson_value_get_integer(jseed)); |
|
112
|
anonSeed = seedBuffer; |
|
113
|
}else if( cson_value_is_string(jseed) ){ |
|
114
|
anonSeed = cson_string_cstr(cson_value_get_string(jseed)); |
|
115
|
} |
|
116
|
} |
|
117
|
if(!anonSeed){ |
|
118
|
g.json.resultCode = preciseErrors |
|
119
|
? FSL_JSON_E_LOGIN_FAILED_NOSEED |
|
120
|
: FSL_JSON_E_LOGIN_FAILED; |
|
121
|
return NULL; |
|
122
|
} |
|
123
|
} |
|
124
|
|
|
125
|
#if 0 |
|
126
|
{ |
|
127
|
/* only for debugging the PD()-incorrect-result problem */ |
|
128
|
cson_object * o = NULL; |
|
129
|
uid = login_search_uid( &name, pw ); |
|
130
|
payload = cson_value_new_object(); |
|
131
|
o = cson_value_get_object(payload); |
|
132
|
cson_object_set( o, "n", cson_value_new_string(name,strlen(name))); |
|
133
|
cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw))); |
|
134
|
return payload; |
|
135
|
} |
|
136
|
#endif |
|
137
|
uid = anonSeed |
|
138
|
? login_is_valid_anonymous(name, pw, anonSeed) |
|
139
|
: login_search_uid(&name, pw) |
|
140
|
; |
|
141
|
if( !uid ){ |
|
142
|
g.json.resultCode = preciseErrors |
|
143
|
? FSL_JSON_E_LOGIN_FAILED_NOTFOUND |
|
144
|
: FSL_JSON_E_LOGIN_FAILED; |
|
145
|
return NULL; |
|
146
|
}else{ |
|
147
|
char * cookie = NULL; |
|
148
|
cson_object * po; |
|
149
|
char * cap = NULL; |
|
150
|
if(anonSeed){ |
|
151
|
login_set_anon_cookie(&cookie, 0); |
|
152
|
}else{ |
|
153
|
login_set_user_cookie(name, uid, &cookie, 0); |
|
154
|
} |
|
155
|
payload = cson_value_new_object(); |
|
156
|
po = cson_value_get_object(payload); |
|
157
|
cson_object_set(po, "authToken", json_new_string(cookie)); |
|
158
|
free(cookie); |
|
159
|
cson_object_set(po, "name", json_new_string(name)); |
|
160
|
cap = db_text(NULL, "SELECT cap FROM user WHERE login=%Q", name); |
|
161
|
cson_object_set(po, "capabilities", |
|
162
|
cap ? json_new_string(cap) : cson_value_null() ); |
|
163
|
free(cap); |
|
164
|
cson_object_set(po, "loginCookieName", |
|
165
|
json_new_string( login_cookie_name() ) ); |
|
166
|
/* TODO: add loginExpiryTime to the payload. To do this properly |
|
167
|
we "should" add an ([unsigned] int *) to |
|
168
|
login_set_user_cookie() and login_set_anon_cookie(), to which |
|
169
|
the expiry time is assigned. (Remember that JSON doesn't do |
|
170
|
unsigned int.) |
|
171
|
|
|
172
|
For non-anonymous users we could also simply query the |
|
173
|
user.cexpire db field after calling login_set_user_cookie(), |
|
174
|
but for anonymous we need to get the time when the cookie is |
|
175
|
set because anon does not get a db entry like normal users |
|
176
|
do. Anonymous cookies currently have a hard-coded lifetime in |
|
177
|
login_set_anon_cookie() (currently 6 hours), which we "should |
|
178
|
arguably" change to use the time configured for non-anonymous |
|
179
|
users (see login_set_user_cookie() for details). |
|
180
|
*/ |
|
181
|
return payload; |
|
182
|
} |
|
183
|
} |
|
184
|
|
|
185
|
/* |
|
186
|
** Impl of /json/logout. |
|
187
|
** |
|
188
|
*/ |
|
189
|
cson_value * json_page_logout(void){ |
|
190
|
cson_value const *token = g.json.authToken; |
|
191
|
/* Remember that json_bootstrap_late() replaces the login cookie |
|
192
|
with the JSON auth token if the request contains it. If the |
|
193
|
request is missing the auth token then this will fetch fossil's |
|
194
|
original cookie. Either way, it's what we want :). |
|
195
|
|
|
196
|
We require the auth token to avoid someone maliciously |
|
197
|
trying to log someone else out (not 100% sure if that |
|
198
|
would be possible, given fossil's hardened cookie, but |
|
199
|
I'll assume it would be for the time being). |
|
200
|
*/ |
|
201
|
; |
|
202
|
if(!token){ |
|
203
|
g.json.resultCode = FSL_JSON_E_MISSING_AUTH; |
|
204
|
}else{ |
|
205
|
login_clear_login_data(); |
|
206
|
g.json.authToken = NULL /* memory is owned elsewhere.*/; |
|
207
|
json_setenv(FossilJsonKeys.authToken, NULL); |
|
208
|
} |
|
209
|
return json_page_whoami(); |
|
210
|
} |
|
211
|
|
|
212
|
/* |
|
213
|
** Implementation of the /json/anonymousPassword page. |
|
214
|
*/ |
|
215
|
cson_value * json_page_anon_password(void){ |
|
216
|
cson_value * v = cson_value_new_object(); |
|
217
|
cson_object * o = cson_value_get_object(v); |
|
218
|
unsigned const int seed = captcha_seed(); |
|
219
|
char const * zCaptcha = captcha_decode(seed, 0); |
|
220
|
cson_object_set(o, "seed", |
|
221
|
cson_value_new_integer( (cson_int_t)seed ) |
|
222
|
); |
|
223
|
cson_object_set(o, "password", |
|
224
|
cson_value_new_string( zCaptcha, strlen(zCaptcha) ) |
|
225
|
); |
|
226
|
return v; |
|
227
|
} |
|
228
|
|
|
229
|
|
|
230
|
|
|
231
|
/* |
|
232
|
** Implements the /json/whoami page/command. |
|
233
|
*/ |
|
234
|
cson_value * json_page_whoami(void){ |
|
235
|
cson_value * payload = NULL; |
|
236
|
cson_object * obj = NULL; |
|
237
|
Stmt q; |
|
238
|
if(!g.json.authToken && g.userUid==0){ |
|
239
|
/* assume we just logged out. */ |
|
240
|
db_prepare(&q, "SELECT login, cap FROM user WHERE login='nobody'"); |
|
241
|
} |
|
242
|
else{ |
|
243
|
db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", |
|
244
|
g.userUid); |
|
245
|
} |
|
246
|
if( db_step(&q)==SQLITE_ROW ){ |
|
247
|
|
|
248
|
/* reminder: we don't use g.zLogin because it's 0 for the guest |
|
249
|
user and the HTML UI appears to currently allow the name to be |
|
250
|
changed (but doing so would break other code). */ |
|
251
|
char const * str; |
|
252
|
payload = cson_value_new_object(); |
|
253
|
obj = cson_value_get_object(payload); |
|
254
|
str = (char const *)sqlite3_column_text(q.pStmt,0); |
|
255
|
if( str ){ |
|
256
|
cson_object_set( obj, "name", |
|
257
|
cson_value_new_string(str,strlen(str)) ); |
|
258
|
} |
|
259
|
str = (char const *)sqlite3_column_text(q.pStmt,1); |
|
260
|
if( str ){ |
|
261
|
cson_object_set( obj, "capabilities", |
|
262
|
cson_value_new_string(str,strlen(str)) ); |
|
263
|
} |
|
264
|
if( g.json.authToken ){ |
|
265
|
cson_object_set( obj, "authToken", g.json.authToken ); |
|
266
|
} |
|
267
|
}else{ |
|
268
|
g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; |
|
269
|
} |
|
270
|
db_finalize(&q); |
|
271
|
return payload; |
|
272
|
} |
|
273
|
#endif /* FOSSIL_ENABLE_JSON */ |
|
274
|
|