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