1
0
Fork 0

added changePassword attribute for users

This commit is contained in:
Frank Celler 2014-03-25 11:38:51 +01:00
parent cc533c5df5
commit d34832bbcf
19 changed files with 1047 additions and 1004 deletions

View File

@ -27,6 +27,8 @@ v2.1.0 (XXXX-XX-XX)
v2.0.1 (XXXX-XX-XX)
-------------------
* added `changePassword` attribute for users
* fixed non-working "save" button in collection edit view of web interface
clicking the save button did nothing. one had to press enter in one of the input
fields to send modified form data

View File

@ -29,39 +29,162 @@ Command-Line Options for the Authentication and Authorisation{#DbaManualAuthenti
Introduction to User Management{#UserManagementIntro}
=====================================================
ArangoDB provides basic functionality to add, modify and remove
database users programmatically. The following functionality is
provided by the `users` module and can be used from inside arangosh
and arangod.
ArangoDB provides basic functionality to add, modify and remove database users
programmatically. The following functionality is provided by the `users` module
and can be used from inside arangosh and arangod.
Please note that this functionality is not available from within the
web interface.
Please note that this functionality is not available from within the web
interface.
@COMMENT{######################################################################}
@anchor UserManagementSave
@copydetails JSF_saveUser
@FUN{users.save(@FA{user}, @FA{passwd}, @FA{active}, @FA{extra}, @FA{changePassword})}
This will create a new ArangoDB user. The username must be specified in
@FA{user} and must not be empty.
The password must be given as a string, too, but can be left empty if required.
If the @FA{active} attribute is not specified, it defaults to `true`. The
@FA{extra} attribute can be used to save custom data with the user.
If the @FA{changePassword} attribute is not specified, it defaults to `false`.
The @FA{changePassword} attribute can be used to indicate that the user must
change has password before logging in.
This method will fail if either the username or the passwords are not specified
or given in a wrong format, or there already exists a user with the specified
name.
The new user account can only be used after the server is either restarted or
the server authentication cache is @ref UserManagementReload "reloaded".
Note: this function will not work from within the web interface
@EXAMPLES
arangosh> require("org/arangodb/users").save("my-user", "my-secret-password");
@COMMENT{######################################################################}
@CLEARPAGE
@anchor UserManagementDocument
@copydetails JSF_documentUser
@FUN{users.document(@FA{user})}
Fetches an existing ArangoDB user from the database.
The username must be specified in @FA{user}.
This method will fail if the user cannot be found in the database.
Note: this function will not work from within the web interface
@COMMENT{######################################################################}
@CLEARPAGE
@anchor UserManagementReplace
@copydetails JSF_replaceUser
@FUN{users.replace(@FA{user}, @FA{passwd}, @FA{active}, @FA{extra}, @FA{changePassword})}
This will look up an existing ArangoDB user and replace its user data.
The username must be specified in @FA{user}, and a user with the specified name
must already exist in the database.
The password must be given as a string, too, but can be left empty if required.
If the @FA{active} attribute is not specified, it defaults to `true`. The
@FA{extra} attribute can be used to save custom data with the user.
If the @FA{changePassword} attribute is not specified, it defaults to `false`.
The @FA{changePassword} attribute can be used to indicate that the user must
change has password before logging in.
This method will fail if either the username or the passwords are not specified
or given in a wrong format, or if the specified user cannot be found in the
database.
Note: this function will not work from within the web interface
@EXAMPLES
arangosh> require("org/arangodb/users").replace("my-user", "my-changed-password");
@COMMENT{######################################################################}
@CLEARPAGE
@anchor UserManagementUpdate
@copydetails JSF_updateUser
@FUN{@FA{users}.update(@FA{user}, @FA{passwd}, @FA{active}, @FA{extra}, @FA{changePassword})}
This will update an existing ArangoDB user with a new password and other data.
The username must be specified in @FA{user} and the user must already exist in
the database.
The password must be given as a string, too, but can be left empty if required.
If the @FA{active} attribute is not specified, the current value saved for the
user will not be changed. The same is true for the @FA{extra} and the
@FA{changePassword} attribute.
This method will fail if either the username or the passwords are not specified
or given in a wrong format, or if the specified user cannot be found in the
database.
Note: this function will not work from within the web interface
@EXAMPLES
arangosh> require("org/arangodb/users").update("my-user", "my-secret-password");
@COMMENT{######################################################################}
@CLEARPAGE
@anchor UserManagementRemove
@copydetails JSF_removeUser
@FUN{users.remove(@FA{user})}
Removes an existing ArangoDB user from the database.
The username must be specified in @FA{user} and the specified user must exist in
the database.
This method will fail if the user cannot be found in the database.
Note: this function will not work from within the web interface
@EXAMPLES
arangosh> require("org/arangodb/users").remove("my-user");
@COMMENT{######################################################################}
@CLEARPAGE
@anchor UserManagementReload
@copydetails JSF_reloadUsers
@FUN{users.reload()}
Reloads the user authentication data on the server
All user authentication data is loaded by the server once on startup only and is
cached after that. When users get added or deleted, a cache flush is required,
and this can be performed by called this method.
Note: this function will not work from within the web interface
@COMMENT{######################################################################}
@CLEARPAGE
@anchor UserManagementIsValid
@copydetails JSF_isValidUsers
@FUN{users.isvalid(@FA{user}, @FA{password})}
Checks whether the given combination of username and password is valid. The
function will return a boolean value if the combination of username and password
is valid.
Each call to this function is penalized by the server sleeping a random
amount of time.
Note: this function will not work from within the web interface
@COMMENT{######################################################################}
@CLEARPAGE
@anchor UserManagementAll
@FUN{users.all()}
Fetches all existing ArangoDB users from the database.
@COMMENT{######################################################################}
@BNAVIGATE_DbaManualAuthentication

View File

@ -8,6 +8,8 @@ TOC {#DbaManualAuthenticationTOC}
- @ref UserManagementSave "users.save"
- @ref UserManagementDocument "users.document"
- @ref UserManagementReplace "users.replace"
- @ref UserManagementUpdate "users.update"
- @ref UserManagementRemove "users.remove"
- @ref UserManagementReload "users.reload"
- @ref UserManagementIsValid "users.isValid"
- @ref UserManagementAll "users.all"

View File

@ -1,20 +1,20 @@
HTTP Interface for User Management{#HttpUser}
==============================================
=============================================
@NAVIGATE_HttpUser
@EMBEDTOC{HttpUserTOC}
User Management{#HttpUserIntro}
================================
===============================
This is an introduction to ArangoDB's Http interface for managing users.
The interface provides a simple means to add, update, and remove users.
All users managed through this interface will be stored in the system
collection `_users`.
The interface provides a simple means to add, update, and remove users. All
users managed through this interface will be stored in the system collection
`_users`.
This specialised interface intentionally does not provide all functionality
that is available in the regular document REST API.
This specialised interface intentionally does not provide all functionality that
is available in the regular document REST API.
Operations on users may become more restricted than regular document operations,
and extra privileges and security security checks may be introduced in the
@ -22,23 +22,279 @@ future for this interface.
Please note that user operations are not included in ArangoDB's replication.
@COMMENT{######################################################################}
@anchor HttpUserSave
@copydetails JSF_post_api_user
@RESTHEADER{POST /_api/user,creates user}
@RESTBODYPARAM{body,json,required}
@RESTDESCRIPTION
The following data need to be passed in a JSON representation in the body of the
POST request:
- `user`: The name of the user as a string. This is mandatory.
- `passwd`: The user password as a string. If no password is specified, the
empty string will be used.
- `active`: an optional flag that specifies whether the user is active. If not
specified, this will default to `true`.
- `extra`: an optional JSON object with arbitrary extra data about the user.
- `changePassword`: an optional flag that specifies whethers the user must
change the password or not. If not specified, this will default to `false`.
If set to `true`, the only operations allowed are `PUT /_api/user` or
`PATCH /_api/user`. All other operations will result in a `HTTP 403`.
If the user can be added by the server, the server will respond with `HTTP 201`.
In case of success, the returned JSON object has the following properties:
- `error`: boolean flag to indicate that an error occurred (`false` in this
case)
- `code`: the HTTP status code
If the JSON representation is malformed or mandatory data is missing from the
request, the server will respond with `HTTP 400`.
The body of the response will contain a JSON object with additional error
details. The object has the following attributes:
- `error`: boolean flag to indicate that an error occurred (`true` in this case)
- `code`: the HTTP status code
- `errorNum`: the server error number
- `errorMessage`: a descriptive error message
@RESTRETURNCODES
@RESTRETURNCODE{201}
returned if the user can be added by the server.
@RESTRETURNCODE{400}
If the JSON representation is malformed or mandatory data is missing from the
request.
@COMMENT{######################################################################}
@CLEARPAGE
@anchor HttpUserReplace
@copydetails JSF_put_api_user
@RESTHEADER{PUT /_api/user/{user},replaces user}
@RESTURLPARAMETERS
@RESTURLPARAM{user,string,required}
The name of the user.
@RESTBODYPARAM{body,json,required}
@RESTDESCRIPTION
Replaces the data of an existing user. The name of an existing user must be
specified in `user`.
The following data can to be passed in a JSON representation in the body of the
POST request:
- `passwd`: The user password as a string. Specifying a password is mandatory,
but the empty string is allowed for passwords.
- `active`: an optional flag that specifies whether the user is active. If not
specified, this will default to `true`.
- `extra`: an optional JSON object with arbitrary extra data about the user.
- `changePassword`: an optional flag that specifies whether the user must change
the password or not. If not specified, this will default to `false`.
If the user can be replaced by the server, the server will respond with `HTTP
200`.
In case of success, the returned JSON object has the following properties:
- `error`: boolean flag to indicate that an error occurred (`false` in this
case)
- `code`: the HTTP status code
If the JSON representation is malformed or mandatory data is missing from the
request, the server will respond with `HTTP 400`. If the specified user does not
exist, the server will respond with `HTTP 404`.
The body of the response will contain a JSON object with additional error
details. The object has the following attributes:
- `error`: boolean flag to indicate that an error occurred (`true` in this case)
- `code`: the HTTP status code
- `errorNum`: the server error number
- `errorMessage`: a descriptive error message
@RESTRETURNCODES
@RESTRETURNCODE{200}
Is returned if the user data can be replaced by the server.
@RESTRETURNCODE{400}
The JSON representation is malformed or mandatory data is missing from the
request.
@RESTRETURNCODE{404}
The specified user does not exist.
@COMMENT{######################################################################}
@CLEARPAGE
@anchor HttpUserUpdate
@copydetails JSF_patch_api_user
@RESTHEADER{PATCH /_api/user/{user},updates user}
@RESTURLPARAMETERS
@RESTURLPARAM{user,string,required}
The name of the user.
@RESTBODYPARAM{body,json,required}
@RESTDESCRIPTION
Partially updates the data of an existing user. The name of an existing user
must be specified in `user`.
The following data can be passed in a JSON representation in the body of the
POST request:
- `passwd`: The user password as a string. Specifying a password is optional. If
not specified, the previously existing value will not be modified.
- `active`: an optional flag that specifies whether the user is active. If not
specified, the previously existing value will not be modified.
- `extra`: an optional JSON object with arbitrary extra data about the user. If
not specified, the previously existing value will not be modified.
- `changePassword`: an optional flag that specifies whether the user must change
the password or not. If not specified, the previously existing value will not
be modified.
If the user can be updated by the server, the server will respond with `HTTP
200`.
In case of success, the returned JSON object has the following properties:
- `error`: boolean flag to indicate that an error occurred (`false` in this
case)
- `code`: the HTTP status code
If the JSON representation is malformed or mandatory data is missing from the
request, the server will respond with `HTTP 400`. If the specified user does not
exist, the server will respond with `HTTP 404`.
The body of the response will contain a JSON object with additional error
details. The object has the following attributes:
- `error`: boolean flag to indicate that an error occurred (`true` in this case)
- `code`: the HTTP status code
- `errorNum`: the server error number
- `errorMessage`: a descriptive error message
@RESTRETURNCODES
@RESTRETURNCODE{200}
Is returned if the user data can be replaced by the server.
@RESTRETURNCODE{400}
The JSON representation is malformed or mandatory data is missing from the
request.
@RESTRETURNCODE{404}
The specified user does not exist.
@COMMENT{######################################################################}
@CLEARPAGE
@anchor HttpUserRemove
@copydetails JSF_delete_api_user
@RESTHEADER{DELETE /_api/user/{user},removes a user}
@RESTURLPARAMETERS
@RESTURLPARAM{user,string,required}
The name of the user.
@RESTDESCRIPTION
Removes an existing user, identified by `user`.
If the user can be removed, the server will respond with `HTTP 202`.
In case of success, the returned JSON object has the following properties:
- `error`: boolean flag to indicate that an error occurred (`false` in this
case)
- `code`: the HTTP status code
If the specified user does not exist, the server will respond with `HTTP 404`.
The body of the response will contain a JSON object with additional error
details. The object has the following attributes:
- `error`: boolean flag to indicate that an error occurred (`true` in this case)
- `code`: the HTTP status code
- `errorNum`: the server error number
- `errorMessage`: a descriptive error message
@RESTRETURNCODES
@RESTRETURNCODE{202}
Is returned if the user was removed by the server.
@RESTRETURNCODE{404}
The specified user does not exist.
@COMMENT{######################################################################}
@CLEARPAGE
@anchor HttpUserDocument
@copydetails JSF_get_api_user
@RESTHEADER{GET /_api/user/{user},fetches a user}
@RESTURLPARAMETERS
@RESTURLPARAM{user,string,required}
The name of the user.
@RESTDESCRIPTION
Fetches data about the specified user.
The call will return a JSON document with at least the following attributes on
success:
- `user`: The name of the user as a string.
- `active`: an optional flag that specifies whether the user is active.
- `extra`: an optional JSON object with arbitrary extra data about the user.
- `changePassword`: an optional flag that specifies whether the user must change
the password or not.
@RESTRETURNCODES
@RESTRETURNCODE{200}
The user was found.
@RESTRETURNCODE{404}
The user with `user` does not exist.
@COMMENT{######################################################################}
@BNAVIGATE_HttpUser

View File

@ -270,6 +270,7 @@ ALIASES += \
# other aliases
ALIASES += \
"VERSION=@PACKAGE_VERSION@" \
"COMMENT{1}=" \
"LIT{1}=<tt>\1</tt>" \
"LIT{2}=<tt>\1, \2</tt>" \
"LIT{3}=<tt>\1, \2, \3</tt>" \

View File

@ -42,18 +42,13 @@ using namespace triagens::arango;
using namespace triagens::rest;
// -----------------------------------------------------------------------------
// --SECTION-- class ArangoServer
// --SECTION-- class VocbaseContext
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// --SECTION-- constructors and destructors
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoDB
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief constructor
////////////////////////////////////////////////////////////////////////////////
@ -77,19 +72,10 @@ VocbaseContext::~VocbaseContext () {
TRI_ReleaseVocBase(_vocbase);
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- public methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoDB
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief whether or not to use special cluster authentication
////////////////////////////////////////////////////////////////////////////////
@ -175,6 +161,7 @@ HttpResponse::HttpResponseCode VocbaseContext::authenticate () {
string const up = StringUtils::decodeBase64(auth);
std::string::size_type n = up.find(':', 0);
if (n == std::string::npos || n == 0 || n + 1 > up.size()) {
LOG_TRACE("invalid authentication data found, cannot extract username/password");
@ -188,49 +175,53 @@ HttpResponse::HttpResponseCode VocbaseContext::authenticate () {
}
#endif
// look up the info in the cache first
char* cached = TRI_CheckCacheAuthInfo(_vocbase, auth);
bool mustChange;
char* cached = TRI_CheckCacheAuthInfo(_vocbase, auth, &mustChange);
string username;
if (cached != 0) {
// found a cached entry, access must be granted
_request->setUser(string(cached));
if (cached != 0) {
username = string(cached);
TRI_Free(TRI_CORE_MEM_ZONE, cached);
return HttpResponse::OK;
}
// no entry found in cache, decode the basic auth info and look it up
else {
string const up = StringUtils::decodeBase64(auth);
std::string::size_type n = up.find(':', 0);
if (n == std::string::npos || n == 0 || n + 1 > up.size()) {
LOG_TRACE("invalid authentication data found, cannot extract username/password");
return HttpResponse::BAD;
}
string const username = up.substr(0, n);
username = up.substr(0, n);
LOG_TRACE("checking authentication for user '%s'", username.c_str());
bool res = TRI_CheckAuthenticationAuthInfo(_vocbase, auth, username.c_str(), up.substr(n + 1).c_str());
bool res = TRI_CheckAuthenticationAuthInfo(
_vocbase, auth, username.c_str(), up.substr(n + 1).c_str(), &mustChange);
if (! res) {
return HttpResponse::UNAUTHORIZED;
}
}
// TODO: create a user object for the VocbaseContext
_request->setUser(username);
if (mustChange) {
if ((_request->requestType() == HttpRequest::HTTP_REQUEST_PUT
|| _request->requestType() == HttpRequest::HTTP_REQUEST_PATCH)
&& TRI_EqualString2(_request->requestPath(), "/_api/user/", 11)) {
return HttpResponse::OK;
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
return HttpResponse::FORBIDDEN;
}
return HttpResponse::OK;
}
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE

View File

@ -40,11 +40,6 @@
// --SECTION-- private functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup VocBase
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief hashes a string
////////////////////////////////////////////////////////////////////////////////
@ -242,6 +237,7 @@ static TRI_vocbase_auth_t* ConvertAuthInfo (TRI_vocbase_t* vocbase,
char* password;
bool active;
bool found;
bool mustChange;
TRI_vocbase_auth_t* result;
shaper = primary->_shaper;
@ -273,6 +269,13 @@ static TRI_vocbase_auth_t* ConvertAuthInfo (TRI_vocbase_t* vocbase,
return NULL;
}
// extract must-change-password flag
mustChange = ExtractBooleanShapedJson(shaper, document, "changePassword", &found);
if (! found) {
mustChange = false;
}
result = TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(TRI_vocbase_auth_t), true);
if (result == NULL) {
@ -286,13 +289,15 @@ static TRI_vocbase_auth_t* ConvertAuthInfo (TRI_vocbase_t* vocbase,
result->_username = user;
result->_password = password;
result->_active = active;
result->_mustChange = mustChange;
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief clears the authentication info
/// the caller must acquire the lock itself
///
/// @note the caller must acquire the lock itself
////////////////////////////////////////////////////////////////////////////////
static void ClearAuthInfo (TRI_vocbase_t* vocbase) {
@ -333,19 +338,10 @@ static void ClearAuthInfo (TRI_vocbase_t* vocbase) {
vocbase->_authCache._nrUsed = 0;
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- public functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup VocBase
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief initialises the authentication info
////////////////////////////////////////////////////////////////////////////////
@ -596,7 +592,8 @@ void TRI_ClearAuthInfo (TRI_vocbase_t* vocbase) {
////////////////////////////////////////////////////////////////////////////////
char* TRI_CheckCacheAuthInfo (TRI_vocbase_t* vocbase,
char const* hash) {
char const* hash,
bool* mustChange) {
TRI_vocbase_auth_cache_t* cached;
char* username;
@ -607,6 +604,7 @@ char* TRI_CheckCacheAuthInfo (TRI_vocbase_t* vocbase,
if (cached != NULL) {
username = TRI_DuplicateStringZ(TRI_CORE_MEM_ZONE, cached->_username);
*mustChange = cached->_mustChange;
}
TRI_ReadUnlockReadWriteLock(&vocbase->_authInfoLock);
@ -622,7 +620,8 @@ char* TRI_CheckCacheAuthInfo (TRI_vocbase_t* vocbase,
bool TRI_CheckAuthenticationAuthInfo (TRI_vocbase_t* vocbase,
char const* hash,
char const* username,
char const* password) {
char const* password,
bool* mustChange) {
TRI_vocbase_auth_t* auth;
bool res;
char* hex;
@ -642,9 +641,12 @@ bool TRI_CheckAuthenticationAuthInfo (TRI_vocbase_t* vocbase,
return false;
}
*mustChange = auth->_mustChange;
// convert password
res = false;
// salted password
if (TRI_IsPrefixString(auth->_password, "$1$")) {
if (strlen(auth->_password) < 12 || auth->_password[11] != '$') {
LOG_WARNING("found corrupted password for user '%s'", username);
@ -673,6 +675,8 @@ bool TRI_CheckAuthenticationAuthInfo (TRI_vocbase_t* vocbase,
TRI_FreeString(TRI_CORE_MEM_ZONE, hex);
}
}
// unsalted password
else {
len = strlen(password);
sha256 = TRI_SHA256String(password, len, &sha256Len);
@ -702,6 +706,7 @@ bool TRI_CheckAuthenticationAuthInfo (TRI_vocbase_t* vocbase,
cached->_hash = TRI_DuplicateStringZ(TRI_CORE_MEM_ZONE, hash);
cached->_username = TRI_DuplicateStringZ(TRI_CORE_MEM_ZONE, username);
cached->_mustChange = auth->_mustChange;
if (cached->_hash == NULL || cached->_username == NULL) {
FreeAuthCacheInfo(cached);
@ -723,10 +728,6 @@ bool TRI_CheckAuthenticationAuthInfo (TRI_vocbase_t* vocbase,
return res;
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------

View File

@ -45,11 +45,6 @@ struct TRI_vocbase_s;
// --SECTION-- public types
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup VocBase
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief authentication and authorisation
////////////////////////////////////////////////////////////////////////////////
@ -58,6 +53,7 @@ typedef struct TRI_vocbase_auth_s {
char* _username;
char* _password;
bool _active;
bool _mustChange;
}
TRI_vocbase_auth_t;
@ -68,22 +64,14 @@ TRI_vocbase_auth_t;
typedef struct TRI_vocbase_auth_cache_s {
char* _hash;
char* _username;
bool _mustChange;
}
TRI_vocbase_auth_cache_t;
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- public functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup VocBase
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief initialises the authentication info
////////////////////////////////////////////////////////////////////////////////
@ -133,20 +121,18 @@ void TRI_ClearAuthInfo (struct TRI_vocbase_s*);
////////////////////////////////////////////////////////////////////////////////
char* TRI_CheckCacheAuthInfo (struct TRI_vocbase_s*,
char const*);
char const* hash,
bool* mustChange);
////////////////////////////////////////////////////////////////////////////////
/// @brief checks the authentication
////////////////////////////////////////////////////////////////////////////////
bool TRI_CheckAuthenticationAuthInfo (struct TRI_vocbase_s*,
char const*,
char const*,
char const*);
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
char const* hash,
char const* username,
char const* password,
bool* mustChange);
#ifdef __cplusplus
}

View File

@ -459,12 +459,14 @@ static int WriteShutdownInfo (TRI_server_t* server) {
static bool CanUseDatabase (TRI_vocbase_t* vocbase,
char const* username,
char const* password) {
bool mustChange;
if (! vocbase->_settings.requireAuthentication) {
// authentication is turned off
return true;
}
return TRI_CheckAuthenticationAuthInfo(vocbase, NULL, username, password);
return TRI_CheckAuthenticationAuthInfo(vocbase, NULL, username, password, &mustChange);
}
////////////////////////////////////////////////////////////////////////////////

View File

@ -1,4 +1,4 @@
/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
/*jslint indent: 2, nomen: true, maxlen: 120, sloppy: true, vars: true, white: true, plusplus: true */
/*global require */
////////////////////////////////////////////////////////////////////////////////
@ -8,7 +8,7 @@
///
/// DISCLAIMER
///
/// Copyright 2012 triagens GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
@ -25,7 +25,7 @@
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
/// @author Copyright 2012-2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var arangodb = require("org/arangodb");
@ -37,42 +37,7 @@ var users = require("org/arangodb/users");
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoAPI
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief fetch a user
///
/// @RESTHEADER{GET /_api/user/{user},fetches a user}
///
/// @RESTURLPARAMETERS
///
/// @RESTURLPARAM{user,string,required}
/// The name of the user.
///
/// @RESTDESCRIPTION
///
/// Fetches data about the specified user.
///
/// The call will return a JSON document with at least the following attributes
/// on success:
///
/// - `user`: The name of the user as a string.
///
/// - `active`: an optional flag that specifies whether the user is active.
///
/// - `extra`: an optional JSON object with arbitrary extra data about the
/// user.
///
/// @RESTRETURNCODES
///
/// @RESTRETURNCODE{200}
/// The user was found.
///
/// @RESTRETURNCODE{404}
/// The user with `user` does not exist.
///
/// @brief fetches a user
////////////////////////////////////////////////////////////////////////////////
function get_api_user (req, res) {
@ -89,8 +54,7 @@ function get_api_user (req, res) {
var user = decodeURIComponent(req.suffix[0]);
try {
var result = users.document(user);
actions.resultOk(req, res, actions.HTTP_OK, result);
actions.resultOk(req, res, actions.HTTP_OK, users.document(user));
}
catch (err) {
if (err.errorNum === arangodb.errors.ERROR_USER_NOT_FOUND.code) {
@ -103,61 +67,7 @@ function get_api_user (req, res) {
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a new user
///
/// @RESTHEADER{POST /_api/user,creates user}
///
/// @RESTBODYPARAM{body,json,required}
///
/// @RESTDESCRIPTION
///
/// The following data need to be passed in a JSON representation in the body of
/// the POST request:
///
/// - `user`: The name of the user as a string. This is mandatory.
///
/// - `passwd`: The user password as a string. If no password is specified,
/// the empty string will be used.
///
/// - `active`: an optional flag that specifies whether the user is active.
/// If not specified, this will default to `true`.
///
/// - `extra`: an optional JSON object with arbitrary extra data about the
/// user.
///
/// If the user can be added by the server, the server will respond with
/// `HTTP 201`.
///
/// In case of success, the returned JSON object has the following properties:
///
/// - `error`: boolean flag to indicate that an error occurred (`false`
/// in this case)
///
/// - `code`: the HTTP status code
///
/// If the JSON representation is malformed or mandatory data is missing from the
/// request, the server will respond with `HTTP 400`.
///
/// The body of the response will contain a JSON object with additional error
/// details. The object has the following attributes:
///
/// - `error`: boolean flag to indicate that an error occurred (`true` in this case)
///
/// - `code`: the HTTP status code
///
/// - `errorNum`: the server error number
///
/// - `errorMessage`: a descriptive error message
///
/// @RESTRETURNCODES
///
/// @RESTRETURNCODE{201}
/// returned if the user can be added by the server.
///
/// @RESTRETURNCODE{400}
/// If the JSON representation is malformed or mandatory data is missing from the
/// request.
///
/// @brief creates a new user
////////////////////////////////////////////////////////////////////////////////
function post_api_user (req, res) {
@ -168,6 +78,7 @@ function post_api_user (req, res) {
}
var user;
if (req.suffix.length === 1) {
// validate if a combination or username / password is valid
user = decodeURIComponent(req.suffix[0]);
@ -189,82 +100,20 @@ function post_api_user (req, res) {
}
user = json.user;
if (user === undefined && json.hasOwnProperty("username")) {
// deprecated usage
user = json.username;
}
users.save(user, json.passwd, json.active, json.extra);
var doc = users.save(user, json.passwd, json.active, json.extra, json.changePassword);
users.reload();
actions.resultOk(req, res, actions.HTTP_CREATED, { });
actions.resultOk(req, res, actions.HTTP_CREATED, doc);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief replace an existing user
///
/// @RESTHEADER{PUT /_api/user/{user},replaces user}
///
/// @RESTURLPARAMETERS
///
/// @RESTURLPARAM{user,string,required}
/// The name of the user.
///
/// @RESTBODYPARAM{body,json,required}
///
/// @RESTDESCRIPTION
///
/// Replaces the data of an existing user. The name of an existing user must
/// be specified in `user`.
///
/// The following data can to be passed in a JSON representation in the body of
/// the POST request:
///
/// - `passwd`: The user password as a string. Specifying a password is
/// mandatory, but the empty string is allowed for passwords.
///
/// - `active`: an optional flag that specifies whether the user is active.
/// If not specified, this will default to `true`.
///
/// - `extra`: an optional JSON object with arbitrary extra data about the
/// user.
///
/// If the user can be replaced by the server, the server will respond with
/// `HTTP 200`.
///
/// In case of success, the returned JSON object has the following properties:
///
/// - `error`: boolean flag to indicate that an error occurred (`false`
/// in this case)
///
/// - `code`: the HTTP status code
///
/// If the JSON representation is malformed or mandatory data is missing from the
/// request, the server will respond with `HTTP 400`. If the specified user
/// does not exist, the server will respond with `HTTP 404`.
///
/// The body of the response will contain a JSON object with additional error
/// details. The object has the following attributes:
///
/// - `error`: boolean flag to indicate that an error occurred (`true` in this case)
///
/// - `code`: the HTTP status code
///
/// - `errorNum`: the server error number
///
/// - `errorMessage`: a descriptive error message
///
/// @RESTRETURNCODES
///
/// @RESTRETURNCODE{200}
/// Is returned if the user data can be replaced by the server.
///
/// @RESTRETURNCODE{400}
/// The JSON representation is malformed or mandatory data is missing from the
/// request.
///
/// @RESTRETURNCODE{404}
/// The specified user does not exist.
/// @brief replaces an existing user
////////////////////////////////////////////////////////////////////////////////
function put_api_user (req, res) {
@ -282,10 +131,10 @@ function put_api_user (req, res) {
}
try {
users.replace(user, json.passwd, json.active, json.extra);
var doc = users.replace(user, json.passwd, json.active, json.extra, json.changePassword);
users.reload();
actions.resultOk(req, res, actions.HTTP_OK, { });
actions.resultOk(req, res, actions.HTTP_OK, doc);
}
catch (err) {
if (err.errorNum === arangodb.errors.ERROR_USER_NOT_FOUND.code) {
@ -298,72 +147,7 @@ function put_api_user (req, res) {
}
////////////////////////////////////////////////////////////////////////////////
/// @brief partially update an existing user
///
/// @RESTHEADER{PATCH /_api/user/{user},updates user}
///
/// @RESTURLPARAMETERS
///
/// @RESTURLPARAM{user,string,required}
/// The name of the user.
///
/// @RESTBODYPARAM{body,json,required}
///
/// @RESTDESCRIPTION
///
/// Partially updates the data of an existing user. The name of an existing user
/// must be specified in `user`.
///
/// The following data can be passed in a JSON representation in the body of
/// the POST request:
///
/// - `passwd`: The user password as a string. Specifying a password is
/// optional. If not specified, the previously existing value will not be
/// modified.
///
/// - `active`: an optional flag that specifies whether the user is active.
/// If not specified, the previously existing value will not be modified.
///
/// - `extra`: an optional JSON object with arbitrary extra data about the
/// user. If not specified, the previously existing value will not be modified.
///
/// If the user can be updated by the server, the server will respond with
/// `HTTP 200`.
///
/// In case of success, the returned JSON object has the following properties:
///
/// - `error`: boolean flag to indicate that an error occurred (`false`
/// in this case)
///
/// - `code`: the HTTP status code
///
/// If the JSON representation is malformed or mandatory data is missing from the
/// request, the server will respond with `HTTP 400`. If the specified user
/// does not exist, the server will respond with `HTTP 404`.
///
/// The body of the response will contain a JSON object with additional error
/// details. The object has the following attributes:
///
/// - `error`: boolean flag to indicate that an error occurred (`true` in this case)
///
/// - `code`: the HTTP status code
///
/// - `errorNum`: the server error number
///
/// - `errorMessage`: a descriptive error message
///
/// @RESTRETURNCODES
///
/// @RESTRETURNCODE{200}
/// Is returned if the user data can be replaced by the server.
///
/// @RESTRETURNCODE{400}
/// The JSON representation is malformed or mandatory data is missing from the
/// request.
///
/// @RESTRETURNCODE{404}
/// The specified user does not exist.
///
/// @brief partially updates an existing user
////////////////////////////////////////////////////////////////////////////////
function patch_api_user (req, res) {
@ -380,9 +164,10 @@ function patch_api_user (req, res) {
}
try {
users.update(user, json.passwd, json.active, json.extra);
var doc = users.update(user, json.passwd, json.active, json.extra, json.changePassword);
users.reload();
actions.resultOk(req, res, actions.HTTP_OK, { });
actions.resultOk(req, res, actions.HTTP_OK, doc);
}
catch (err) {
if (err.errorNum === arangodb.errors.ERROR_USER_NOT_FOUND.code) {
@ -395,50 +180,7 @@ function patch_api_user (req, res) {
}
////////////////////////////////////////////////////////////////////////////////
/// @brief remove an existing user
///
/// @RESTHEADER{DELETE /_api/user/{user},removes a user}
///
/// @RESTURLPARAMETERS
///
/// @RESTURLPARAM{user,string,required}
/// The name of the user.
///
/// @RESTDESCRIPTION
///
/// Removes an existing user, identified by `user`.
///
/// If the user can be removed, the server will respond with `HTTP 202`.
///
/// In case of success, the returned JSON object has the following properties:
///
/// - `error`: boolean flag to indicate that an error occurred (`false`
/// in this case)
///
/// - `code`: the HTTP status code
///
/// If the specified user does not exist, the server will respond with
/// `HTTP 404`.
///
/// The body of the response will contain a JSON object with additional error
/// details. The object has the following attributes:
///
/// - `error`: boolean flag to indicate that an error occurred (`true` in this case)
///
/// - `code`: the HTTP status code
///
/// - `errorNum`: the server error number
///
/// - `errorMessage`: a descriptive error message
///
/// @RESTRETURNCODES
///
/// @RESTRETURNCODE{202}
/// Is returned if the user was removed by the server.
///
/// @RESTRETURNCODE{404}
/// The specified user does not exist.
///
/// @brief removes an existing user
////////////////////////////////////////////////////////////////////////////////
function delete_api_user (req, res) {
@ -449,9 +191,10 @@ function delete_api_user (req, res) {
var user = decodeURIComponent(req.suffix[0]);
try {
users.remove(user);
var doc = users.remove(user);
users.reload();
actions.resultOk(req, res, actions.HTTP_ACCEPTED, { });
actions.resultOk(req, res, actions.HTTP_ACCEPTED, doc);
}
catch (err) {
if (err.errorNum === arangodb.errors.ERROR_USER_NOT_FOUND.code) {
@ -508,11 +251,11 @@ actions.defineHttp({
}
});
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
// End:

View File

@ -172,6 +172,7 @@
"ERROR_USER_INVALID_PASSWORD" : { "code" : 1701, "message" : "invalid password" },
"ERROR_USER_DUPLICATE" : { "code" : 1702, "message" : "duplicate user" },
"ERROR_USER_NOT_FOUND" : { "code" : 1703, "message" : "user not found" },
"ERROR_USER_CHANGE_PASSWORD" : { "code" : 1704, "message" : "user must change his password" },
"ERROR_APPLICATION_INVALID_NAME" : { "code" : 1750, "message" : "invalid application name" },
"ERROR_APPLICATION_INVALID_MOUNT" : { "code" : 1751, "message" : "invalid mount" },
"ERROR_APPLICATION_DOWNLOAD_FAILED" : { "code" : 1752, "message" : "application download failed" },

View File

@ -1,4 +1,4 @@
/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
/*jslint indent: 2, nomen: true, maxlen: 120, sloppy: true, vars: true, white: true, plusplus: true */
/*global require, exports */
////////////////////////////////////////////////////////////////////////////////
@ -8,7 +8,7 @@
///
/// DISCLAIMER
///
/// Copyright 2012 triagens GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
@ -25,19 +25,12 @@
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
/// @author Copyright 2012-2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var internal = require("internal");
var arangodb = require("org/arangodb");
var arangosh = require("org/arangodb/arangosh");
var base = require("org/arangodb/users-common");
var i;
for (i in base) {
if (base.hasOwnProperty(i)) {
exports[i] = base[i];
}
}
// -----------------------------------------------------------------------------
// --SECTION-- module "org/arangodb/users"
@ -48,10 +41,146 @@ for (i in base) {
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
/// @brief creates a new user
////////////////////////////////////////////////////////////////////////////////
exports.save = function (user, passwd, active, extra, changePassword) {
var db = internal.db;
var uri = "_api/user/";
var data = {user: user};
if (passwd !== undefined) {
data.passwd = passwd;
}
if (active !== undefined) {
data.active = active;
}
if (extra !== undefined) {
data.extra = extra;
}
if (changePassword !== undefined) {
data.changePassword = changePassword;
}
var requestResult = db._connection.POST(uri, JSON.stringify(data));
return arangosh.checkRequestResult(requestResult);
};
////////////////////////////////////////////////////////////////////////////////
/// @brief replaces an existing user
////////////////////////////////////////////////////////////////////////////////
exports.replace = function (user, passwd, active, extra, changePassword) {
var db = internal.db;
var uri = "_api/user/" + encodeURIComponent(user);
var data = {
passwd: passwd,
active: active,
extra: extra,
changePassword: changePassword
};
var requestResult = db._connection.PUT(uri, JSON.stringify(data));
return arangosh.checkRequestResult(requestResult);
};
////////////////////////////////////////////////////////////////////////////////
/// @brief updates an existing user
////////////////////////////////////////////////////////////////////////////////
exports.update = function (user, passwd, active, extra, changePassword) {
var db = internal.db;
var uri = "_api/user/" + encodeURIComponent(user);
var data = {};
if (passwd !== undefined) {
data.passwd = passwd;
}
if (active !== undefined) {
data.active = active;
}
if (extra !== undefined) {
data.extra = extra;
}
if (changePassword !== undefined) {
data.changePassword = changePassword;
}
var requestResult = db._connection.PATCH(uri, JSON.stringify(data));
return arangosh.checkRequestResult(requestResult);
};
////////////////////////////////////////////////////////////////////////////////
/// @brief deletes an existing user
////////////////////////////////////////////////////////////////////////////////
exports.remove = function (user) {
var db = internal.db;
var uri = "_api/user/" + encodeURIComponent(user);
var requestResult = db._connection.DELETE(uri);
return arangosh.checkRequestResult(requestResult);
};
////////////////////////////////////////////////////////////////////////////////
/// @brief gets an existing user
////////////////////////////////////////////////////////////////////////////////
exports.document = function (user) {
var db = internal.db;
var uri = "_api/user/" + encodeURIComponent(user);
var requestResult = db._connection.GET(uri);
return arangosh.checkRequestResult(requestResult);
};
////////////////////////////////////////////////////////////////////////////////
/// @brief checks whether a combination of username / password is valid.
////////////////////////////////////////////////////////////////////////////////
exports.isValid = function (user, password) {
var db = internal.db;
var uri = "_api/user/" + encodeURIComponent(user);
var data = { passwd: password };
var requestResult = db._connection.POST(uri, JSON.stringify(data));
if (requestResult.error !== undefined && requestResult.error) {
if (requestResult.errorNum === arangodb.errors.ERROR_USER_NOT_FOUND.code) {
return false;
}
return arangosh.checkRequestResult(requestResult);
}
return requestResult.result;
};
////////////////////////////////////////////////////////////////////////////////
/// @brief gets all existing users
////////////////////////////////////////////////////////////////////////////////
exports.all = function () {
var db = internal.db;
var uri = "_api/user";
var requestResult = db._connection.GET(uri);
return arangosh.checkRequestResult(requestResult).result;
};
////////////////////////////////////////////////////////////////////////////////
/// @brief reloads the user authentication data
////////////////////////////////////////////////////////////////////////////////
@ -63,16 +192,12 @@ exports.reload = function () {
arangosh.checkRequestResult(requestResult);
};
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\|/\\*jslint"
// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
// End:

View File

@ -172,6 +172,7 @@
"ERROR_USER_INVALID_PASSWORD" : { "code" : 1701, "message" : "invalid password" },
"ERROR_USER_DUPLICATE" : { "code" : 1702, "message" : "duplicate user" },
"ERROR_USER_NOT_FOUND" : { "code" : 1703, "message" : "user not found" },
"ERROR_USER_CHANGE_PASSWORD" : { "code" : 1704, "message" : "user must change his password" },
"ERROR_APPLICATION_INVALID_NAME" : { "code" : 1750, "message" : "invalid application name" },
"ERROR_APPLICATION_INVALID_MOUNT" : { "code" : 1751, "message" : "invalid mount" },
"ERROR_APPLICATION_DOWNLOAD_FAILED" : { "code" : 1752, "message" : "application download failed" },

View File

@ -1,534 +0,0 @@
/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
/*global require, exports */
////////////////////////////////////////////////////////////////////////////////
/// @brief User management
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2012 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var internal = require("internal"); // OK: time
var arangodb = require("org/arangodb");
var crypto = require("org/arangodb/crypto");
var _ = require("underscore");
var db = arangodb.db;
var ArangoError = arangodb.ArangoError;
// -----------------------------------------------------------------------------
// --SECTION-- module "org/arangodb/users"
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// --SECTION-- private functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief encode password using SHA256
////////////////////////////////////////////////////////////////////////////////
var encodePassword = function (password) {
var salt;
var encoded;
var random = crypto.rand();
if (random === undefined) {
random = "time:" + internal.time();
}
else {
random = "random:" + random;
}
salt = crypto.sha256(random);
salt = salt.substr(0,8);
encoded = "$1$" + salt + "$" + crypto.sha256(salt + password);
return encoded;
};
////////////////////////////////////////////////////////////////////////////////
/// @brief validate a username
////////////////////////////////////////////////////////////////////////////////
var validateName = function (username) {
if (typeof username !== 'string' || username === '') {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_INVALID_NAME.code;
err.errorMessage = arangodb.errors.ERROR_USER_INVALID_NAME.message;
throw err;
}
};
////////////////////////////////////////////////////////////////////////////////
/// @brief validate password
////////////////////////////////////////////////////////////////////////////////
var validatePassword = function (passwd) {
if (typeof passwd !== 'string') {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_INVALID_PASSWORD.code;
err.errorMessage = arangodb.errors.ERROR_USER_INVALID_PASSWORD.message;
throw err;
}
};
////////////////////////////////////////////////////////////////////////////////
/// @brief return the users collection
////////////////////////////////////////////////////////////////////////////////
var getStorage = function () {
var users = db._collection("_users");
if (users === null) {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_ARANGO_COLLECTION_NOT_FOUND.code;
err.errorMessage = "collection _users not found";
throw err;
}
return users;
};
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- public functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_saveUser
/// @brief create a new user
///
/// @FUN{users.save(@FA{user}, @FA{passwd}, @FA{active}, @FA{extra})}
///
/// This will create a new ArangoDB user. The username must be specified in
/// @FA{user} and must not be empty.
///
/// The password must be given as a string, too, but can be left empty if
/// required.
///
/// If the @FA{active} attribute is not specified, it defaults to @LIT{true}.
/// The @FA{extra} attribute can be used to save custom data with the user.
///
/// This method will fail if either the username or the passwords are not
/// specified or given in a wrong format, or there already exists a user with
/// the specified name.
///
/// The new user account can only be used after the server is either restarted
/// or the server authentication cache is @ref UserManagementReload "reloaded".
///
/// Note: this function will not work from within the web interface
///
/// @EXAMPLES
///
/// @code
/// arangosh> require("org/arangodb/users").save("my-user", "my-secret-password");
/// arangosh> require("org/arangodb/users").reload();
/// @endcode
////////////////////////////////////////////////////////////////////////////////
exports.save = function (user, passwd, active, extra) {
if (passwd === null || passwd === undefined) {
passwd = "";
}
// validate input
validateName(user);
validatePassword(passwd);
if (active === undefined || active === null) {
// this is the default value for active
active = true;
}
var users = getStorage();
var previous = users.firstExample({ user: user });
if (previous === null) {
var hash = encodePassword(passwd);
var data = {
user: user,
password: hash,
active: active
};
if (extra !== undefined) {
data.extra = extra;
}
var doc = users.save(data);
// not exports.reload() as this is an abstract method...
require("org/arangodb/users").reload();
return doc;
}
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_DUPLICATE.code;
err.errorMessage = arangodb.errors.ERROR_USER_DUPLICATE.message;
throw err;
};
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_replaceUser
/// @brief replace an existing user
///
/// @FUN{users.replace(@FA{user}, @FA{passwd}, @FA{active}, @FA{extra})}
///
/// This will look up an existing ArangoDB user and replace its user data.
///
/// The username must be specified in @FA{user}, and a user with the specified
/// name must already exist in the database.
///
/// The password must be given as a string, too, but can be left empty if
/// required.
///
/// If the @FA{active} attribute is not specified, it defaults to @LIT{true}.
/// The @FA{extra} attribute can be used to save custom data with the user.
///
/// This method will fail if either the username or the passwords are not
/// specified or given in a wrong format, or if the specified user cannot be
/// found in the database.
///
/// The replace is effective only after the server is either restarted
/// or the server authentication cache is reloaded (see @ref JSF_reloadUsers).
///
/// Note: this function will not work from within the web interface
///
/// @EXAMPLES
///
/// @code
/// arangosh> require("org/arangodb/users").replace("my-user", "my-changed-password");
/// arangosh> require("org/arangodb/users").reload();
/// @endcode
////////////////////////////////////////////////////////////////////////////////
exports.replace = function (user, passwd, active, extra) {
if (passwd === null || passwd === undefined) {
passwd = "";
}
// validate input
validateName(user);
validatePassword(passwd);
if (active === undefined || active === null) {
// this is the default
active = true;
}
var users = getStorage();
var previous = users.firstExample({ user: user });
if (previous === null) {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_NOT_FOUND.code;
err.errorMessage = arangodb.errors.ERROR_USER_NOT_FOUND.message;
throw err;
}
var hash = encodePassword(passwd);
var data = {
user: user,
password: hash,
active: active
};
if (extra !== undefined) {
data.extra = extra;
}
var doc = users.replace(previous, data);
// not exports.reload() as this is an abstract method...
require("org/arangodb/users").reload();
return doc;
};
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_updateUser
/// @brief update an existing user
///
/// @FUN{@FA{users}.update(@FA{user}, @FA{passwd}, @FA{active}, @FA{extra})}
///
/// This will update an existing ArangoDB user with a new password and other
/// data.
///
/// The username must be specified in @FA{user} and the user must already exist
/// in the database.
///
/// The password must be given as a string, too, but can be left empty if
/// required.
///
/// If the @FA{active} attribute is not specified, the current value saved for
/// the user will not be changed. The same is true for the @FA{extra} attribute.
///
/// This method will fail if either the username or the passwords are not
/// specified or given in a wrong format, or if the specified user cannot be
/// found in the database.
///
/// The update is effective only after the server is either restarted
/// or the server authentication cache is reloaded (see @ref JSF_reloadUsers).
///
/// Note: this function will not work from within the web interface
///
/// @EXAMPLES
///
/// @code
/// arangosh> require("org/arangodb/users").replace("my-user", "my-secret-password");
/// arangosh> require("org/arangodb/users").reload();
/// @endcode
////////////////////////////////////////////////////////////////////////////////
exports.update = function (user, passwd, active, extra) {
// validate input
validateName(user);
if (passwd !== undefined) {
validatePassword(passwd);
}
var users = getStorage();
var previous = users.firstExample({ user: user });
if (previous === null) {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_NOT_FOUND.code;
err.errorMessage = arangodb.errors.ERROR_USER_NOT_FOUND.message;
throw err;
}
var data = previous._shallowCopy;
if (passwd !== undefined) {
var hash = encodePassword(passwd);
data.password = hash;
}
if (active !== undefined && active !== null) {
data.active = active;
}
if (extra !== undefined) {
data.extra = extra;
}
var doc = users.update(previous, data);
// not exports.reload() as this is an abstract method...
require("org/arangodb/users").reload();
return doc;
};
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_removeUser
/// @brief delete an existing user
///
/// @FUN{users.remove(@FA{user})}
///
/// Removes an existing ArangoDB user from the database.
///
/// The username must be specified in @FA{user} and the specified user must
/// exist in the database.
///
/// This method will fail if the user cannot be found in the database.
///
/// The deletion is effective only after the server is either restarted
/// or the server authentication cache is @ref UserManagementReload "reloaded".
///
/// Note: this function will not work from within the web interface
///
/// @EXAMPLES
///
/// @code
/// arangosh> require("org/arangodb/users").remove("my-user");
/// arangosh> require("org/arangodb/users").reload();
/// @endcode
////////////////////////////////////////////////////////////////////////////////
exports.remove = function (user) {
// validate input
validateName(user);
var users = getStorage();
var previous = users.firstExample({ user: user });
if (previous === null) {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_NOT_FOUND.code;
err.errorMessage = arangodb.errors.ERROR_USER_NOT_FOUND.message;
throw err;
}
var doc = users.remove(previous);
// not exports.reload() as this is an abstract method...
require("org/arangodb/users").reload();
return doc;
};
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_documentUser
/// @brief get an existing user
///
/// @FUN{users.document(@FA{user})}
///
/// Fetches an existing ArangoDB user from the database.
///
/// The username must be specified in @FA{user}.
/// This method will fail if the user cannot be found in the database.
///
/// Note: this function will not work from within the web interface
////////////////////////////////////////////////////////////////////////////////
exports.document = function (user) {
// validate name
validateName(user);
var users = getStorage();
var previous = users.firstExample({ user: user });
if (previous === null) {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_NOT_FOUND.code;
err.errorMessage = arangodb.errors.ERROR_USER_NOT_FOUND.message;
throw err;
}
return {
user: previous.user,
active: previous.active,
extra: previous.extra || { }
};
};
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_allUser
/// @brief gets all existing users
///
/// @FUN{users.all()}
///
/// Fetches all existing ArangoDB users from the database.
////////////////////////////////////////////////////////////////////////////////
exports.all = function () {
var cursor = getStorage().all();
var result = [ ];
while (cursor.hasNext()) {
var doc = cursor.next();
var user = {
user: doc.user,
active: doc.active,
extra: doc.extra || { }
};
result.push(user);
}
return result;
};
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_isValidUsers
/// @brief checks whether a combination of username / password is valid.
///
/// @FUN{users.isvalid(@FA{user}, @FA{password})}
///
/// Checks whether the given combination of username and password is valid.
/// The function will return a boolean value if the combination of username
/// and password is valid.
///
/// Each call to this function is penalized by the server sleeping a random
/// amount of time.
///
/// Note: this function will not work from within the web interface
////////////////////////////////////////////////////////////////////////////////
exports.isValid = function (user, password) {
var users = getStorage();
var previous = users.firstExample({ user: user });
if (previous === null || ! previous.active) {
return false;
}
var salted = previous.password.substr(3, 8) + password;
var hex = crypto.sha256(salted);
// penalize the call
internal.sleep(Math.random());
return (previous.password.substr(12) === hex);
};
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_reloadUsers
/// @brief reloads the user authentication data
///
/// @FUN{users.reload()}
///
/// Reloads the user authentication data on the server
///
/// All user authentication data is loaded by the server once on startup only
/// and is cached after that. When users get added or deleted, a cache flush is
/// required, and this can be performed by called this method.
///
/// Note: this function will not work from within the web interface
////////////////////////////////////////////////////////////////////////////////
exports.reload = function () {
throw "cannot use abstract reload function";
};
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\|/\\*jslint"
// End:

View File

@ -1,4 +1,4 @@
/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
/*jslint indent: 2, nomen: true, maxlen: 120, sloppy: true, vars: true, white: true, plusplus: true */
/*global require, exports, ArangoAgency */
////////////////////////////////////////////////////////////////////////////////
@ -8,7 +8,7 @@
///
/// DISCLAIMER
///
/// Copyright 2012 triagens GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
@ -25,33 +25,351 @@
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
/// @author Copyright 2012-2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var internal = require("internal"); // OK: reloadAuth
var base = require("org/arangodb/users-common");
var arangodb = require("org/arangodb");
var crypto = require("org/arangodb/crypto");
// copy exports from base
var i;
for (i in base) {
if (base.hasOwnProperty(i)) {
exports[i] = base[i];
}
}
var db = arangodb.db;
var ArangoError = arangodb.ArangoError;
// -----------------------------------------------------------------------------
// --SECTION-- module "org/arangodb/users"
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// --SECTION-- private functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief encode password using SHA256
////////////////////////////////////////////////////////////////////////////////
var encodePassword = function (password) {
var salt;
var encoded;
var random = crypto.rand();
if (random === undefined) {
random = "time:" + internal.time();
}
else {
random = "random:" + random;
}
salt = crypto.sha256(random);
salt = salt.substr(0,8);
encoded = "$1$" + salt + "$" + crypto.sha256(salt + password);
return encoded;
};
////////////////////////////////////////////////////////////////////////////////
/// @brief validates a username
////////////////////////////////////////////////////////////////////////////////
var validateName = function (username) {
if (typeof username !== 'string' || username === '') {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_INVALID_NAME.code;
err.errorMessage = arangodb.errors.ERROR_USER_INVALID_NAME.message;
throw err;
}
};
////////////////////////////////////////////////////////////////////////////////
/// @brief validates password
////////////////////////////////////////////////////////////////////////////////
var validatePassword = function (passwd) {
if (typeof passwd !== 'string') {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_INVALID_PASSWORD.code;
err.errorMessage = arangodb.errors.ERROR_USER_INVALID_PASSWORD.message;
throw err;
}
};
////////////////////////////////////////////////////////////////////////////////
/// @brief returns the users collection
////////////////////////////////////////////////////////////////////////////////
var getStorage = function () {
var users = db._collection("_users");
if (users === null) {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_ARANGO_COLLECTION_NOT_FOUND.code;
err.errorMessage = "collection _users not found";
throw err;
}
return users;
};
// -----------------------------------------------------------------------------
// --SECTION-- public functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
/// @brief creates a new user
////////////////////////////////////////////////////////////////////////////////
exports.save = function (user, passwd, active, extra, changePassword) {
if (passwd === null || passwd === undefined) {
passwd = "";
}
// validate input
validateName(user);
validatePassword(passwd);
if (active === undefined || active === null) {
active = true; // this is the default value
}
if (active === undefined || active === null) {
active = true; // this is the default value
}
var users = getStorage();
var previous = users.firstExample({ user: user });
if (previous === null) {
var hash = encodePassword(passwd);
var data = {
user: user,
password: hash,
active: active,
changePassword: changePassword
};
if (extra !== undefined) {
data.extra = extra;
}
var doc = users.save(data);
// not exports.reload() as this is an abstract method...
require("org/arangodb/users").reload();
return users.document(doc._id);
}
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_DUPLICATE.code;
err.errorMessage = arangodb.errors.ERROR_USER_DUPLICATE.message;
throw err;
};
////////////////////////////////////////////////////////////////////////////////
/// @brief replaces an existing user
////////////////////////////////////////////////////////////////////////////////
exports.replace = function (user, passwd, active, extra, changePassword) {
if (passwd === null || passwd === undefined) {
passwd = "";
}
// validate input
validateName(user);
validatePassword(passwd);
if (active === undefined || active === null) {
active = true; // this is the default
}
if (changePassword === undefined || changePassword === null) {
changePassword = false; // this is the default
}
var users = getStorage();
var previous = users.firstExample({ user: user });
if (previous === null) {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_NOT_FOUND.code;
err.errorMessage = arangodb.errors.ERROR_USER_NOT_FOUND.message;
throw err;
}
var hash = encodePassword(passwd);
var data = {
user: user,
password: hash,
active: active,
changePassword: changePassword
};
if (extra !== undefined) {
data.extra = extra;
}
users.replace(previous, data);
// not exports.reload() as this is an abstract method...
require("org/arangodb/users").reload();
return users.document(previous._id);
};
////////////////////////////////////////////////////////////////////////////////
/// @brief updates an existing user
////////////////////////////////////////////////////////////////////////////////
exports.update = function (user, passwd, active, extra, changePassword) {
// validate input
validateName(user);
if (passwd !== undefined) {
validatePassword(passwd);
}
var users = getStorage();
var previous = users.firstExample({ user: user });
if (previous === null) {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_NOT_FOUND.code;
err.errorMessage = arangodb.errors.ERROR_USER_NOT_FOUND.message;
throw err;
}
var data = previous._shallowCopy;
if (passwd !== undefined) {
var hash = encodePassword(passwd);
data.password = hash;
}
if (active !== undefined && active !== null) {
data.active = active;
}
if (extra !== undefined) {
data.extra = extra;
}
if (changePassword !== undefined && changePassword !== null) {
data.changePassword = changePassword;
}
users.update(previous, data);
// not exports.reload() as this is an abstract method...
require("org/arangodb/users").reload();
return users.document(previous._id);
};
////////////////////////////////////////////////////////////////////////////////
/// @brief deletes an existing user
////////////////////////////////////////////////////////////////////////////////
exports.remove = function (user) {
// validate input
validateName(user);
var users = getStorage();
var previous = users.firstExample({ user: user });
if (previous === null) {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_NOT_FOUND.code;
err.errorMessage = arangodb.errors.ERROR_USER_NOT_FOUND.message;
throw err;
}
var doc = users.remove(previous);
// not exports.reload() as this is an abstract method...
require("org/arangodb/users").reload();
return doc;
};
////////////////////////////////////////////////////////////////////////////////
/// @brief gets an existing user
////////////////////////////////////////////////////////////////////////////////
exports.document = function (user) {
// validate name
validateName(user);
var users = getStorage();
var doc = users.firstExample({ user: user });
if (doc === null) {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_NOT_FOUND.code;
err.errorMessage = arangodb.errors.ERROR_USER_NOT_FOUND.message;
throw err;
}
return {
user: doc.user,
active: doc.active,
extra: doc.extra || {},
changePassword: doc.changePassword
};
};
////////////////////////////////////////////////////////////////////////////////
/// @brief checks whether a combination of username / password is valid.
////////////////////////////////////////////////////////////////////////////////
exports.isValid = function (user, password) {
var users = getStorage();
var previous = users.firstExample({ user: user });
if (previous === null || ! previous.active) {
return false;
}
var salted = previous.password.substr(3, 8) + password;
var hex = crypto.sha256(salted);
// penalize the call
internal.sleep(Math.random());
return (previous.password.substr(12) === hex);
};
////////////////////////////////////////////////////////////////////////////////
/// @brief gets all existing users
////////////////////////////////////////////////////////////////////////////////
exports.all = function () {
var cursor = getStorage().all();
var result = [ ];
while (cursor.hasNext()) {
var doc = cursor.next();
var user = {
user: doc.user,
active: doc.active,
extra: doc.extra || { },
changePassword: doc.changePassword
};
result.push(user);
}
return result;
};
////////////////////////////////////////////////////////////////////////////////
/// @brief reloads the user authentication data
////////////////////////////////////////////////////////////////////////////////
@ -83,16 +401,12 @@ exports.reload = function () {
}
};
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\|/\\*jslint"
// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
// End:

View File

@ -228,10 +228,11 @@ ERROR_TRANSACTION_DISALLOWED_OPERATION,1653,"disallowed operation inside transac
## User management
################################################################################
ERROR_USER_INVALID_NAME,1700,"invalid user name","Will be raised when an invalid user name is used"
ERROR_USER_INVALID_PASSWORD,1701,"invalid password","Will be raised when an invalid password is used"
ERROR_USER_DUPLICATE,1702,"duplicate user","Will be raised when a user name already exists"
ERROR_USER_NOT_FOUND,1703,"user not found","Will be raised when a user name is updated that does not exist"
ERROR_USER_INVALID_NAME,1700,"invalid user name","Will be raised when an invalid user name is used."
ERROR_USER_INVALID_PASSWORD,1701,"invalid password","Will be raised when an invalid password is used."
ERROR_USER_DUPLICATE,1702,"duplicate user","Will be raised when a user name already exists."
ERROR_USER_NOT_FOUND,1703,"user not found","Will be raised when a user name is updated that does not exist."
ERROR_USER_CHANGE_PASSWORD,1704,"user must change his password","Will be raised when the user must change his password."
################################################################################
## Application management

View File

@ -168,6 +168,7 @@ void TRI_InitialiseErrorMessages (void) {
REG_ERROR(ERROR_USER_INVALID_PASSWORD, "invalid password");
REG_ERROR(ERROR_USER_DUPLICATE, "duplicate user");
REG_ERROR(ERROR_USER_NOT_FOUND, "user not found");
REG_ERROR(ERROR_USER_CHANGE_PASSWORD, "user must change his password");
REG_ERROR(ERROR_APPLICATION_INVALID_NAME, "invalid application name");
REG_ERROR(ERROR_APPLICATION_INVALID_MOUNT, "invalid mount");
REG_ERROR(ERROR_APPLICATION_DOWNLOAD_FAILED, "application download failed");

View File

@ -393,13 +393,15 @@ extern "C" {
/// Will be raised when a disallowed operation is carried out in a
/// transaction.
/// - 1700: @LIT{invalid user name}
/// Will be raised when an invalid user name is used
/// Will be raised when an invalid user name is used.
/// - 1701: @LIT{invalid password}
/// Will be raised when an invalid password is used
/// Will be raised when an invalid password is used.
/// - 1702: @LIT{duplicate user}
/// Will be raised when a user name already exists
/// Will be raised when a user name already exists.
/// - 1703: @LIT{user not found}
/// Will be raised when a user name is updated that does not exist
/// Will be raised when a user name is updated that does not exist.
/// - 1704: @LIT{user must change his password}
/// Will be raised when the user must change his password.
/// - 1750: @LIT{invalid application name}
/// Will be raised when an invalid application name is specified.
/// - 1751: @LIT{invalid mount}
@ -2102,7 +2104,7 @@ void TRI_InitialiseErrorMessages (void);
///
/// invalid user name
///
/// Will be raised when an invalid user name is used
/// Will be raised when an invalid user name is used.
////////////////////////////////////////////////////////////////////////////////
#define TRI_ERROR_USER_INVALID_NAME (1700)
@ -2112,7 +2114,7 @@ void TRI_InitialiseErrorMessages (void);
///
/// invalid password
///
/// Will be raised when an invalid password is used
/// Will be raised when an invalid password is used.
////////////////////////////////////////////////////////////////////////////////
#define TRI_ERROR_USER_INVALID_PASSWORD (1701)
@ -2122,7 +2124,7 @@ void TRI_InitialiseErrorMessages (void);
///
/// duplicate user
///
/// Will be raised when a user name already exists
/// Will be raised when a user name already exists.
////////////////////////////////////////////////////////////////////////////////
#define TRI_ERROR_USER_DUPLICATE (1702)
@ -2132,11 +2134,21 @@ void TRI_InitialiseErrorMessages (void);
///
/// user not found
///
/// Will be raised when a user name is updated that does not exist
/// Will be raised when a user name is updated that does not exist.
////////////////////////////////////////////////////////////////////////////////
#define TRI_ERROR_USER_NOT_FOUND (1703)
////////////////////////////////////////////////////////////////////////////////
/// @brief 1704: ERROR_USER_CHANGE_PASSWORD
///
/// user must change his password
///
/// Will be raised when the user must change his password.
////////////////////////////////////////////////////////////////////////////////
#define TRI_ERROR_USER_CHANGE_PASSWORD (1704)
////////////////////////////////////////////////////////////////////////////////
/// @brief 1750: ERROR_APPLICATION_INVALID_NAME
///

View File

@ -592,7 +592,7 @@ namespace triagens {
// not found
else if (authResult == HttpResponse::NOT_FOUND) {
HttpResponse response(HttpResponse::NOT_FOUND);
HttpResponse response(authResult);
response.setContentType("application/json; charset=utf-8");
response.body().appendText("{\"error\":true,\"errorMessage\":\"")
@ -607,6 +607,21 @@ namespace triagens {
this->resetState();
}
// forbidden
else if (authResult == HttpResponse::FORBIDDEN) {
HttpResponse response(authResult);
response.setContentType("application/json; charset=utf-8");
response.body().appendText("{\"error\":true,\"errorMessage\":\"change password\",\"code\":")
.appendInteger((int) authResult)
.appendText(",\"errorNum\":")
.appendInteger(TRI_ERROR_USER_CHANGE_PASSWORD)
.appendText("}");
this->handleResponse(&response);
this->resetState();
}
// not authenticated
else {
const string realm = "basic realm=\"" + this->_server->getHandlerFactory()->authenticationRealm(this->_request) + "\"";