Beispiel #1
0
/*
** Impl of /json/user/save.
*/
static cson_value * json_user_save(){
  /* try to get user info from GET/CLI args and construct
     a JSON form of it... */
  cson_object * u = cson_new_object();
  char const * str = NULL;
  char b = -1;
  int i = -1;
  int uid = -1;
  cson_value * payload = NULL;
  /* String properties... */
#define PROP(LK,SK) str = json_find_option_cstr(LK,NULL,SK);     \
  if(str){ cson_object_set(u, LK, json_new_string(str)); } (void)0
  PROP("name","n");
  PROP("password","p");
  PROP("info","i");
  PROP("capabilities","c");
#undef PROP
  /* Boolean properties... */
#define PROP(LK,DFLT) b = json_find_option_bool(LK,NULL,NULL,DFLT);     \
  if(DFLT!=b){ cson_object_set(u, LK, cson_value_new_bool(b)); } (void)0
  PROP("forceLogout",-1);
#undef PROP

#define PROP(LK,DFLT) i = json_find_option_int(LK,NULL,NULL,DFLT);   \
  if(DFLT != i){ cson_object_set(u, LK, cson_value_new_integer(i)); } (void)0
  PROP("uid",-99);
#undef PROP
  if( g.json.reqPayload.o ){
    cson_object_merge( u, g.json.reqPayload.o, CSON_MERGE_NO_RECURSE );
  }
  json_user_update_from_json( u );
  if(!g.json.resultCode){
    uid = cson_value_get_integer( cson_object_get(u, "uid") );
    assert((uid>0) && "Something went wrong in json_user_update_from_json()");
    payload = json_load_user_by_id(uid);
  }
  cson_free_object(u);
  return payload;
}
cson_value * json_artifact_file(cson_object * zParent, int rid){
  cson_object * pay = NULL;
  Stmt q = empty_Stmt;
  cson_array * checkin_arr = NULL;
  char contentFormat;
  i64 contentSize = -1;
  char * parentUuid;
  if( ! g.perm.Read ){
    json_set_err(FSL_JSON_E_DENIED,
                 "Requires 'o' privileges.");
    return NULL;
  }
  
  pay = zParent;

  contentFormat = json_artifact_get_content_format_flag();
  if( 0 != contentFormat ){
    Blob content = empty_blob;
    const char *zMime;
    char const * zFormat = (contentFormat<1) ? "raw" : "html";
    content_get(rid, &content);
    zMime = mimetype_from_content(&content);
    cson_object_set(zParent, "contentType",
                    json_new_string(zMime ? zMime : "text/plain"));
    if(!zMime){/* text/plain */
      if(0 < blob_size(&content)){
        if( 0 < contentFormat ){/*HTML-size it*/
          Blob html = empty_blob;
          wiki_convert(&content, &html, 0);
          assert( blob_size(&content) < blob_size(&html) );
          blob_swap( &html, &content );
          assert( blob_size(&content) > blob_size(&html) );
          blob_reset( &html );
        }/*else as-is*/
      }
      cson_object_set(zParent, "content",
                      cson_value_new_string(blob_str(&content),
                                            (unsigned int)blob_size(&content)));
    }/*else binary: ignore*/
    contentSize = blob_size(&content);
    cson_object_set(zParent, "contentSize", json_new_int(contentSize) );
    cson_object_set(zParent, "contentFormat", json_new_string(zFormat) );
    blob_reset(&content);
  }
  contentSize = db_int64(-1, "SELECT size FROM blob WHERE rid=%d", rid);
  assert( -1 < contentSize );
  cson_object_set(zParent, "size", json_new_int(contentSize) );

  parentUuid = db_text(NULL,
                       "SELECT DISTINCT p.uuid "
                       "FROM blob p, blob f, mlink m "
                       "WHERE m.pid=p.rid "
                       "AND m.fid=f.rid "
                       "AND f.rid=%d",
                       rid
                       );
  if(parentUuid){
    cson_object_set( zParent, "parent", json_new_string(parentUuid) );
    fossil_free(parentUuid);
  }
  
  /* Find checkins associated with this file... */
  db_prepare(&q,
      "SELECT filename.name AS name, "
      "  (mlink.pid==0) AS isNew,"
      "  (mlink.fid==0) AS isDel,"
      "  cast(strftime('%%s',event.mtime) as int) AS timestamp,"
      "  coalesce(event.ecomment,event.comment) as comment,"
      "  coalesce(event.euser,event.user) as user,"
#if 0
      "  a.size AS size," /* same for all checkins. */
#endif
      "  b.uuid as checkin, "
#if 0
      "  mlink.mperm as mperm,"
#endif
      "  coalesce((SELECT value FROM tagxref"
                      "  WHERE tagid=%d AND tagtype>0 AND "
                      " rid=mlink.mid),'trunk') as branch"
      "  FROM mlink, filename, event, blob a, blob b"
      " WHERE filename.fnid=mlink.fnid"
      "   AND event.objid=mlink.mid"
      "   AND a.rid=mlink.fid"
      "   AND b.rid=mlink.mid"
      "   AND mlink.fid=%d"
      "   ORDER BY filename.name, event.mtime",
      TAG_BRANCH, rid
    );
  /* TODO: add a "state" flag for the file in each checkin,
     e.g. "modified", "new", "deleted".
   */
  checkin_arr = cson_new_array(); 
  cson_object_set(pay, "checkins", cson_array_value(checkin_arr));
  while( (SQLITE_ROW==db_step(&q) ) ){
    cson_object * row = cson_value_get_object(cson_sqlite3_row_to_object(q.pStmt));
    /* FIXME: move this isNew/isDel stuff into an SQL CASE statement. */
    char const isNew = cson_value_get_bool(cson_object_get(row,"isNew"));
    char const isDel = cson_value_get_bool(cson_object_get(row,"isDel"));
    cson_object_set(row, "isNew", NULL);
    cson_object_set(row, "isDel", NULL);
    cson_object_set(row, "state",
                    json_new_string(json_artifact_status_to_string(isNew, isDel)));
    cson_array_append( checkin_arr, cson_object_value(row) );
  }
  db_finalize(&q);
  return cson_object_value(pay);
}
Beispiel #3
0
/*
** Expects pUser to contain fossil user fields in JSON form: name,
** uid, info, capabilities, password.
**
** At least one of (name, uid) must be included. All others are
** optional and their db fields will not be updated if those fields
** are not included in pUser.
**
** If uid is specified then name may refer to a _new_ name
** for a user, otherwise the name must refer to an existing user.
** If uid=-1 then the name must be specified and a new user is
** created (fails if one already exists).
**
** If uid is not set, this function might modify pUser to contain the
** db-found (or inserted) user ID.
**
** On error g.json's error state is set and one of the FSL_JSON_E_xxx
** values from FossilJsonCodes is returned.
**
** On success the db record for the given user is updated.
**
** Requires either Admin, Setup, or Password access. Non-admin/setup
** users can only change their own information. Non-setup users may
** not modify the 's' permission. Admin users without setup
** permissions may not edit any other user who has the 's' permission.
**
*/
int json_user_update_from_json( cson_object * pUser ){
#define CSTR(X) cson_string_cstr(cson_value_get_string( cson_object_get(pUser, X ) ))
  char const * zName = CSTR("name");
  char const * zNameNew = zName;
  char * zNameFree = NULL;
  char const * zInfo = CSTR("info");
  char const * zCap = CSTR("capabilities");
  char const * zPW = CSTR("password");
  cson_value const * forceLogout = cson_object_get(pUser, "forceLogout");
  int gotFields = 0;
#undef CSTR
  cson_int_t uid = cson_value_get_integer( cson_object_get(pUser, "uid") );
  char const tgtHasSetup = zCap && (NULL!=strchr(zCap, 's'));
  char tgtHadSetup = 0;
  Blob sql = empty_blob;
  Stmt q = empty_Stmt;

#if 0
  if(!g.perm.Admin && !g.perm.Setup && !g.perm.Password){
    return json_set_err( FSL_JSON_E_DENIED,
                         "Password change requires 'a', 's', "
                         "or 'p' permissions.");
  }
#endif
  if(uid<=0 && (!zName||!*zName)){
    return json_set_err(FSL_JSON_E_MISSING_ARGS,
                        "One of 'uid' or 'name' is required.");
  }else if(uid>0){
    zNameFree = db_text(NULL, "SELECT login FROM user WHERE uid=%d",uid);
    if(!zNameFree){
      return json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
                          "No login found for uid %d.", uid);
    }
    zName = zNameFree;
  }else if(-1==uid){
    /* try to create a new user */
    if(!g.perm.Admin && !g.perm.Setup){
      json_set_err(FSL_JSON_E_DENIED,
                   "Requires 'a' or 's' privileges.");
      goto error;
    }else if(!zName || !*zName){
      json_set_err(FSL_JSON_E_MISSING_ARGS,
                   "No name specified for new user.");
      goto error;
    }else if( db_exists("SELECT 1 FROM user WHERE login=%Q", zName) ){
      json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS,
                   "User %s already exists.", zName);
      goto error;
    }else{
      Stmt ins = empty_Stmt;
      db_prepare(&ins, "INSERT INTO user (login) VALUES(%Q)",zName);
      db_step( &ins );
      db_finalize(&ins);
      uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName);
      assert(uid>0);
      zNameNew = zName;
      cson_object_set( pUser, "uid", cson_value_new_integer(uid) );
    }
  }else{
    uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName);
    if(uid<=0){
      json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
                   "No login found for user [%s].", zName);
      goto error;
    }
    cson_object_set( pUser, "uid", cson_value_new_integer(uid) );
  }

  /* Maintenance note: all error-returns from here on out should go
     via 'goto error' in order to clean up.
  */
  
  if(uid != g.userUid){
    if(!g.perm.Admin && !g.perm.Setup){
      json_set_err(FSL_JSON_E_DENIED,
                   "Changing another user's data requires "
                   "'a' or 's' privileges.");
      goto error;
    }
  }
  /* check if the target uid currently has setup rights. */
  tgtHadSetup = db_int(0,"SELECT 1 FROM user where uid=%d"
                       " AND cap GLOB '*s*'", uid);

  if((tgtHasSetup || tgtHadSetup) && !g.perm.Setup){
    /*
      Do not allow a non-setup user to set or remove setup
      privileges. setup.c uses similar logic.
    */
    json_set_err(FSL_JSON_E_DENIED,
                 "Modifying 's' users/privileges requires "
                 "'s' privileges.");
    goto error;
  }
  /*
    Potential todo: do not allow a setup user to remove 's' from
    himself, to avoid locking himself out?
  */

  blob_append(&sql, "UPDATE user SET",-1 );
  blob_append(&sql, " mtime=cast(strftime('%s') AS INTEGER)", -1);

  if((uid>0) && zNameNew){
    /* Check for name change... */
    if(0!=strcmp(zName,zNameNew)){
      if( (!g.perm.Admin && !g.perm.Setup)
          && (zName != zNameNew)){
        json_set_err( FSL_JSON_E_DENIED,
                      "Modifying user names requires 'a' or 's' privileges.");
        goto error;
      }
      forceLogout = cson_value_true()
        /* reminders: 1) does not allocate.
           2) we do this because changing a name
           invalidates any login token because the old name
           is part of the token hash.
        */;
      blob_append_sql(&sql, ", login=%Q", zNameNew);
      ++gotFields;
    }
  }

  if( zCap && *zCap ){
    if(!g.perm.Admin || !g.perm.Setup){
      /* we "could" arguably silently ignore cap in this case. */
      json_set_err(FSL_JSON_E_DENIED,
                   "Changing capabilities requires 'a' or 's' privileges.");
      goto error;
    }
    blob_append_sql(&sql, ", cap=%Q", zCap);
    ++gotFields;
  }

  if( zPW && *zPW ){
    if(!g.perm.Admin && !g.perm.Setup && !g.perm.Password){
      json_set_err( FSL_JSON_E_DENIED,
                    "Password change requires 'a', 's', "
                    "or 'p' permissions.");
      goto error;
    }else{
#define TRY_LOGIN_GROUP 0 /* login group support is not yet implemented. */
#if !TRY_LOGIN_GROUP
      char * zPWHash = NULL;
      ++gotFields;
      zPWHash = sha1_shared_secret(zPW, zNameNew ? zNameNew : zName, NULL);
      blob_append_sql(&sql, ", pw=%Q", zPWHash);
      free(zPWHash);
#else
      ++gotFields;
      blob_append_sql(&sql, ", pw=coalesce(shared_secret(%Q,%Q,"
                   "(SELECT value FROM config WHERE name='project-code')))",
                   zPW, zNameNew ? zNameNew : zName);
      /* shared_secret() func is undefined? */
#endif
    }
  }

  if( zInfo ){
    blob_append_sql(&sql, ", info=%Q", zInfo);
    ++gotFields;
  }

  if((g.perm.Admin || g.perm.Setup)
     && forceLogout && cson_value_get_bool(forceLogout)){
    blob_append(&sql, ", cookie=NULL, cexpire=NULL", -1);
    ++gotFields;
  }
  
  if(!gotFields){
    json_set_err( FSL_JSON_E_MISSING_ARGS,
                  "Required user data are missing.");
    goto error;
  }
  assert(uid>0);
#if !TRY_LOGIN_GROUP
  blob_append_sql(&sql, " WHERE uid=%d", uid);
#else /* need name for login group support :/ */
  blob_append_sql(&sql, " WHERE login=%Q", zName);
#endif
#if 0
  puts(blob_str(&sql));
  cson_output_FILE( cson_object_value(pUser), stdout, NULL );
#endif
  db_prepare(&q, "%s", blob_sql_text(&sql));
  db_exec(&q);
  db_finalize(&q);
#if TRY_LOGIN_GROUP
  if( zPW || cson_value_get_bool(forceLogout) ){
    Blob groupSql = empty_blob;
    char * zErr = NULL;
    blob_append_sql(&groupSql,
      "INSERT INTO user(login)"
      "  SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);",
      zName, zName
    );
    blob_append(&groupSql, blob_str(&sql), blob_size(&sql));
    login_group_sql(blob_str(&groupSql), NULL, NULL, &zErr);
    blob_reset(&groupSql);
    if( zErr ){
      json_set_err( FSL_JSON_E_UNKNOWN,
                    "Repo-group update at least partially failed: %s",
                    zErr);
      free(zErr);
      goto error;
    }
  }
#endif /* TRY_LOGIN_GROUP */

#undef TRY_LOGIN_GROUP

  free( zNameFree );
  blob_reset(&sql);
  return 0;

  error:
  assert(0 != g.json.resultCode);
  free(zNameFree);
  blob_reset(&sql);
  return g.json.resultCode;
}
Beispiel #4
0
static cson_value * json_timeline_branch(){
  cson_value * pay = NULL;
  Blob sql = empty_blob;
  Stmt q = empty_Stmt;
  int limit = 0;
  if(!g.perm.Read){
    json_set_err(FSL_JSON_E_DENIED,
                 "Requires 'o' permissions.");
    return NULL;
  }
  json_timeline_temp_table();
  blob_append(&sql,
              "SELECT"
              "  blob.rid AS rid,"
              "  uuid AS uuid,"
              "  CAST(strftime('%s',event.mtime) AS INTEGER) as timestamp,"
              "  coalesce(ecomment, comment) as comment,"
              "  coalesce(euser, user) as user,"
              "  blob.rid IN leaf as isLeaf,"
              "  bgcolor as bgColor"
              " FROM event JOIN blob"
              " WHERE blob.rid=event.objid",
              -1);

  blob_append_sql(&sql,
               " AND event.type='ci'"
               " AND blob.rid IN (SELECT rid FROM tagxref"
               "  WHERE tagtype>0 AND tagid=%d AND srcid!=0)"
               " ORDER BY event.mtime DESC",
               TAG_BRANCH);
  limit = json_timeline_limit(20);
  if(limit>0){
    blob_append_sql(&sql," LIMIT %d ",limit);
  }
  db_prepare(&q,"%s", blob_sql_text(&sql));
  blob_reset(&sql);
  pay = json_stmt_to_array_of_obj(&q, NULL);
  db_finalize(&q);
  assert(NULL != pay);
  if(pay){
    /* get the array-form tags of each record. */
    cson_string * tags = cson_new_string("tags",4);
    cson_string * isLeaf = cson_new_string("isLeaf",6);
    cson_array * ar = cson_value_get_array(pay);
    cson_object * outer = NULL;
    unsigned int i = 0;
    unsigned int len = cson_array_length_get(ar);
    cson_value_add_reference( cson_string_value(tags) );
    cson_value_add_reference( cson_string_value(isLeaf) );
    for( ; i < len; ++i ){
      cson_object * row = cson_value_get_object(cson_array_get(ar,i));
      int rid = cson_value_get_integer(cson_object_get(row,"rid"));
      assert( rid > 0 );
      cson_object_set_s(row, tags, json_tags_for_checkin_rid(rid,0));
      cson_object_set_s(row, isLeaf,
                        json_value_to_bool(cson_object_get(row,"isLeaf")));
      cson_object_set(row, "rid", NULL)
        /* remove rid - we don't really want it to be public */;
    }
    cson_value_free( cson_string_value(tags) );
    cson_value_free( cson_string_value(isLeaf) );

    /* now we wrap the payload in an outer shell, for consistency with
       other /json/timeline/xyz APIs...
    */
    outer = cson_new_object();
    if(limit>0){
      cson_object_set( outer, "limit", json_new_int(limit) );
    }
    cson_object_set( outer, "timeline", pay );
    pay = cson_object_value(outer);
  }
  return pay;
}