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 ** drh@hwaci.com
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 */
json_page_login()31 cson_value * json_page_login(){
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 sprintf(seedBuffer, "%"CSON_INT_T_PFMT, cson_value_get_integer(jseed));
111 anonSeed = seedBuffer;
112 }else if( cson_value_is_string(jseed) ){
113 anonSeed = cson_string_cstr(cson_value_get_string(jseed));
114 }
115 }
116 if(!anonSeed){
117 g.json.resultCode = preciseErrors
118 ? FSL_JSON_E_LOGIN_FAILED_NOSEED
119 : FSL_JSON_E_LOGIN_FAILED;
120 return NULL;
121 }
122 }
123
124 #if 0
125 {
126 /* only for debugging the PD()-incorrect-result problem */
127 cson_object * o = NULL;
128 uid = login_search_uid( &name, pw );
129 payload = cson_value_new_object();
130 o = cson_value_get_object(payload);
131 cson_object_set( o, "n", cson_value_new_string(name,strlen(name)));
132 cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw)));
133 return payload;
134 }
135 #endif
136 uid = anonSeed
137 ? login_is_valid_anonymous(name, pw, anonSeed)
138 : login_search_uid(&name, pw)
139 ;
140 if( !uid ){
141 g.json.resultCode = preciseErrors
142 ? FSL_JSON_E_LOGIN_FAILED_NOTFOUND
143 : FSL_JSON_E_LOGIN_FAILED;
144 return NULL;
145 }else{
146 char * cookie = NULL;
147 cson_object * po;
148 char * cap = NULL;
149 if(anonSeed){
150 login_set_anon_cookie(NULL, &cookie, 0);
151 }else{
152 login_set_user_cookie(name, uid, &cookie, 0);
153 }
154 payload = cson_value_new_object();
155 po = cson_value_get_object(payload);
156 cson_object_set(po, "authToken", json_new_string(cookie));
157 free(cookie);
158 cson_object_set(po, "name", json_new_string(name));
159 cap = db_text(NULL, "SELECT cap FROM user WHERE login=%Q", name);
160 cson_object_set(po, "capabilities", cap ? json_new_string(cap) : cson_value_null() );
161 free(cap);
162 cson_object_set(po, "loginCookieName", json_new_string( login_cookie_name() ) );
163 /* TODO: add loginExpiryTime to the payload. To do this properly
164 we "should" add an ([unsigned] int *) to
165 login_set_user_cookie() and login_set_anon_cookie(), to which
166 the expiry time is assigned. (Remember that JSON doesn't do
167 unsigned int.)
168
169 For non-anonymous users we could also simply query the
170 user.cexpire db field after calling login_set_user_cookie(),
171 but for anonymous we need to get the time when the cookie is
172 set because anon does not get a db entry like normal users
173 do. Anonymous cookies currently have a hard-coded lifetime in
174 login_set_anon_cookie() (currently 6 hours), which we "should
175 arguably" change to use the time configured for non-anonymous
176 users (see login_set_user_cookie() for details).
177 */
178 return payload;
179 }
180 }
181
182 /*
183 ** Impl of /json/logout.
184 **
185 */
json_page_logout()186 cson_value * json_page_logout(){
187 cson_value const *token = g.json.authToken;
188 /* Remember that json_bootstrap_late() replaces the login cookie
189 with the JSON auth token if the request contains it. If the
190 request is missing the auth token then this will fetch fossil's
191 original cookie. Either way, it's what we want :).
192
193 We require the auth token to avoid someone maliciously
194 trying to log someone else out (not 100% sure if that
195 would be possible, given fossil's hardened cookie, but
196 I'll assume it would be for the time being).
197 */
198 ;
199 if(!token){
200 g.json.resultCode = FSL_JSON_E_MISSING_AUTH;
201 }else{
202 login_clear_login_data();
203 g.json.authToken = NULL /* memory is owned elsewhere.*/;
204 json_setenv(FossilJsonKeys.authToken, NULL);
205 }
206 return json_page_whoami();
207 }
208
209 /*
210 ** Implementation of the /json/anonymousPassword page.
211 */
json_page_anon_password()212 cson_value * json_page_anon_password(){
213 cson_value * v = cson_value_new_object();
214 cson_object * o = cson_value_get_object(v);
215 unsigned const int seed = captcha_seed();
216 char const * zCaptcha = captcha_decode(seed);
217 cson_object_set(o, "seed",
218 cson_value_new_integer( (cson_int_t)seed )
219 );
220 cson_object_set(o, "password",
221 cson_value_new_string( zCaptcha, strlen(zCaptcha) )
222 );
223 return v;
224 }
225
226
227
228 /*
229 ** Implements the /json/whoami page/command.
230 */
json_page_whoami()231 cson_value * json_page_whoami(){
232 cson_value * payload = NULL;
233 cson_object * obj = NULL;
234 Stmt q;
235 if(!g.json.authToken && g.userUid==0){
236 /* assume we just logged out. */
237 db_prepare(&q, "SELECT login, cap FROM user WHERE login='nobody'");
238 }
239 else{
240 db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d",
241 g.userUid);
242 }
243 if( db_step(&q)==SQLITE_ROW ){
244
245 /* reminder: we don't use g.zLogin because it's 0 for the guest
246 user and the HTML UI appears to currently allow the name to be
247 changed (but doing so would break other code). */
248 char const * str;
249 payload = cson_value_new_object();
250 obj = cson_value_get_object(payload);
251 str = (char const *)sqlite3_column_text(q.pStmt,0);
252 if( str ){
253 cson_object_set( obj, "name",
254 cson_value_new_string(str,strlen(str)) );
255 }
256 str = (char const *)sqlite3_column_text(q.pStmt,1);
257 if( str ){
258 cson_object_set( obj, "capabilities",
259 cson_value_new_string(str,strlen(str)) );
260 }
261 if( g.json.authToken ){
262 cson_object_set( obj, "authToken", g.json.authToken );
263 }
264 }else{
265 g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
266 }
267 db_finalize(&q);
268 return payload;
269 }
270 #endif /* FOSSIL_ENABLE_JSON */
271