1
0
Fork 0
Added a REST API for user management
The REST API exposes the functionality available in module "users" for HTTP access.

The following methods are available:
* GET /_api/user/username: fetch user information
* POST /_api/user: create new user
* PUT /_api/user: replace an existing user
* PATCH /_api/user: (partially) update an existing user
* DELETE /_api/user: remove an existing user

Additionally, a module "crypto" is introduced and exposed for Javascript actions.
The crypto module provides the following functions:
* require("org/arangodb/crypto").md5();
* require("org/arangodb/crypto").sha256();
* require("org/arangodb/crypto").rand();
This commit is contained in:
Jan Steemann 2013-02-20 11:56:26 +01:00
parent a67164b3b5
commit 3fdebb38ee
19 changed files with 1473 additions and 66 deletions

View File

@ -1,6 +1,13 @@
v1.2.beta3 (XXXX-XX-XX)
-----------------------
* issue #393: added REST API for user management at /_api/user
* issue #393, #128: added simple cryptographic functions for user actions in module "crypto":
* require("org/arangodb/crypto").md5()
* require("org/arangodb/crypto").sha256()
* require("org/arangodb/crypto").rand()
* added replaceByExample() Javascript and REST API method
* added updateByExample() Javascript and REST API method

View File

@ -13,8 +13,8 @@ will allow the administrator to restrict access to collections and queries to
certain users, given them either read or write access.
Currently, you can only secure the access to ArangoDB in an all-or-nothing
fashion. The collection `_users` contains all user and the SHA256 of their
passwords. A user can be active or inactive. A typical document of this
fashion. The collection `_users` contains all users and a salted SHA256 hash
of their passwords. A user can be active or inactive. A typical document of this
collection is
{
@ -23,7 +23,7 @@ collection is
"_key" : "1675886"
"active" : true,
"user" : "admin",
"password" : "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918"
"password" : "$1$96b646a9$c2162af67f82e1dc95877b9475ebc55f7abdacad58c2b57848b2bc84acf8ebb6"
}
Command-Line Options for the Authentication and Authorisation {#DbaManualAuthenticationCommandLine}
@ -45,10 +45,18 @@ web interface.
@anchor UserManagementSave
@copydetails JSF_saveUser
@CLEARPAGE
@anchor UserManagementDocument
@copydetails JSF_documentUser
@CLEARPAGE
@anchor UserManagementReplace
@copydetails JSF_replaceUser
@CLEARPAGE
@anchor UserManagementUpdate
@copydetails JSF_updateUser
@CLEARPAGE
@anchor UserManagementRemove
@copydetails JSF_removeUser

View File

@ -3,5 +3,7 @@ TOC {#UserManagementTOC}
- @ref UserManagementIntro
- @ref UserManagementSave "users.save"
- @ref UserManagementDocument "users.document"
- @ref UserManagementReplace "users.replace"
- @ref UserManagementUpdate "users.update"
- @ref UserManagementRemove "users.remove"

View File

@ -0,0 +1,31 @@
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.
@anchor HttpUserSave
@copydetails JSF_POST_api_user
@CLEARPAGE
@anchor HttpUserReplace
@copydetails JSF_PUT_api_user
@CLEARPAGE
@anchor HttpUserUpdate
@copydetails JSF_PATCH_api_user
@CLEARPAGE
@anchor HttpUserRemove
@copydetails JSF_DELETE_api_user
@CLEARPAGE
@anchor HttpUserDocument
@copydetails JSF_GET_api_user

View File

@ -0,0 +1,10 @@
TOC {#HttpUserTOC}
===================
- @ref HttpUser
- @ref HttpUserIntro
- @ref HttpUserSave "POST /_api/user"
- @ref HttpUserReplace "PUT /_api/user/username"
- @ref HttpUserUpdate "PATCH /_api/user/username"
- @ref HttpUserRemove "DELETE /_api/user/username"
- @ref HttpUserDocument "GET /_api/user/username"

View File

@ -19,6 +19,7 @@ ArangoDB for API Implementors (@VERSION) {#ImplementorManual}
@CHAPTER_REF{HttpImport}
@CHAPTER_REF{HttpBatch}
@CHAPTER_REF{HttpSystem}
@CHAPTER_REF{HttpUser}
@CHAPTER_REF{HttpMisc}
@CHAPTER_REF{Communication}
@CHAPTER_REF{NamingConventions}

View File

@ -49,6 +49,7 @@ DOXYGEN = \
Doxygen/js/actions/system/api-query.c \
Doxygen/js/actions/system/api-simple.c \
Doxygen/js/actions/system/api-system.c \
Doxygen/js/actions/system/api-user.c \
Doxygen/js/common/bootstrap/module-console.c \
Doxygen/js/common/bootstrap/module-fs.c \
Doxygen/js/common/bootstrap/modules.c \
@ -98,6 +99,7 @@ WIKI = \
HttpQuery \
HttpSimple \
HttpSystem \
HttpUser \
ImpManual \
ImpManualBasics \
ImplementorManual \
@ -131,7 +133,6 @@ WIKI = \
SimpleQueries \
Upgrading11 \
Upgrading12 \
UserManagement \
UserManual \
UserManualActions \
UserManualArangosh \

View File

@ -0,0 +1,420 @@
# coding: utf-8
require 'rspec'
require './arangodb.rb'
describe ArangoDB do
api = "/_api/user"
prefix = "api-users"
context "user management:" do
before do
(0...10).each{|i|
ArangoDB.delete("/_api/user/users-" + i.to_s);
}
end
after do
(0...10).each{|i|
ArangoDB.delete("/_api/user/users-" + i.to_s);
}
end
################################################################################
## adding users
################################################################################
context "adding users" do
it "add user, no username" do
body = "{ \"passwd\" : \"fox\" }"
doc = ArangoDB.log_post("#{prefix}-add", api, :body => body)
doc.code.should eq(400)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400)
doc.parsed_response['errorNum'].should eq(1700)
end
it "add user, empty username" do
body = "{ \"username\" : \"\", \"passwd\" : \"fox\" }"
doc = ArangoDB.log_post("#{prefix}-add", api, :body => body)
doc.code.should eq(400)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400)
doc.parsed_response['errorNum'].should eq(1700)
end
it "add user, no passwd" do
body = "{ \"username\" : \"users-1\" }"
doc = ArangoDB.log_post("#{prefix}-add", api, :body => body)
doc.code.should eq(201)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(201)
doc = ArangoDB.get(api + "/users-1")
doc.code.should eq(200)
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['user'].should eq("users-1")
doc.parsed_response['active'].should eq(true)
end
it "add user, username and passwd" do
body = "{ \"username\" : \"users-1\", \"passwd\" : \"fox\" }"
doc = ArangoDB.log_post("#{prefix}-add", api, :body => body)
doc.code.should eq(201)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(201)
doc = ArangoDB.get(api + "/users-1")
doc.code.should eq(200)
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['user'].should eq("users-1")
doc.parsed_response['active'].should eq(true)
end
it "add user, username passwd, active, extra" do
body = "{ \"username\" : \"users-2\", \"passwd\" : \"fox\", \"active\" : false, \"extra\" : { \"foo\" : true } }"
doc = ArangoDB.log_post("#{prefix}-add", api, :body => body)
doc.code.should eq(201)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(201)
doc = ArangoDB.get(api + "/users-2")
doc.code.should eq(200)
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['user'].should eq("users-2")
doc.parsed_response['active'].should eq(false)
doc.parsed_response['extra'].should eq({ "foo" => true })
end
it "add user, duplicate username" do
body = "{ \"username\" : \"users-1\", \"passwd\" : \"fox\" }"
doc = ArangoDB.log_post("#{prefix}-add", api, :body => body)
doc.code.should eq(201)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(201)
doc = ArangoDB.log_post("#{prefix}-add", api, :body => body)
doc.code.should eq(400)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400)
doc.parsed_response['errorNum'].should eq(1702)
end
end
################################################################################
## replacing users
################################################################################
context "replacing users" do
it "replace, no user" do
doc = ArangoDB.log_put("#{prefix}-replace", api)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400)
end
it "replace non-existing user" do
doc = ArangoDB.log_put("#{prefix}-replace", api + "/users-1")
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(404)
end
it "replace already removed user" do
body = "{ \"username\" : \"users-1\", \"passwd\" : \"fox\", \"active\" : true, \"extra\" : { \"foo\" : true } }"
doc = ArangoDB.log_post("#{prefix}-replace", api, :body => body)
doc.code.should eq(201)
# remove
doc = ArangoDB.log_delete("#{prefix}-replace", api + "/users-1")
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(202)
# now replace
doc = ArangoDB.log_put("#{prefix}-replace", api + "/users-1")
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(404)
end
it "replace, empty body" do
body = "{ \"username\" : \"users-1\", \"passwd\" : \"fox\", \"active\" : true, \"extra\" : { \"foo\" : true } }"
doc = ArangoDB.log_post("#{prefix}-replace", api, :body => body)
# replace
body = "{ }"
doc = ArangoDB.log_put("#{prefix}-replace", api + "/users-1", :body => body)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc = ArangoDB.get(api + "/users-1")
doc.code.should eq(200)
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['user'].should eq("users-1")
doc.parsed_response['active'].should eq(true)
end
it "replace existing user, no passwd" do
body = "{ \"username\" : \"users-1\", \"passwd\" : \"fox\", \"active\" : true, \"extra\" : { \"foo\" : true } }"
doc = ArangoDB.log_post("#{prefix}-replace", api, :body => body)
# replace
body = "{ \"active\" : false, \"extra\" : { \"foo\" : false } }"
doc = ArangoDB.log_put("#{prefix}-replace", api + "/users-1", :body => body)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc = ArangoDB.get(api + "/users-1")
doc.code.should eq(200)
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['user'].should eq("users-1")
doc.parsed_response['active'].should eq(false)
doc.parsed_response['extra'].should eq({ "foo" => false })
end
it "replace existing user" do
body = "{ \"username\" : \"users-1\", \"passwd\" : \"fox\", \"active\" : true, \"extra\" : { \"foo\" : true } }"
doc = ArangoDB.log_post("#{prefix}-replace", api, :body => body)
# replace
body = "{ \"passwd\" : \"fox2\", \"active\" : false, \"extra\" : { \"foo\" : false } }"
doc = ArangoDB.log_put("#{prefix}-replace", api + "/users-1", :body => body)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc = ArangoDB.get(api + "/users-1")
doc.code.should eq(200)
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['user'].should eq("users-1")
doc.parsed_response['active'].should eq(false)
doc.parsed_response['extra'].should eq({ "foo" => false })
end
end
################################################################################
## updating users
################################################################################
context "updating users" do
it "update, no user" do
doc = ArangoDB.log_patch("#{prefix}-update", api)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400)
end
it "update non-existing user" do
doc = ArangoDB.log_patch("#{prefix}-update", api + "/users-1")
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(404)
end
it "update already removed user" do
body = "{ \"username\" : \"users-1\", \"passwd\" : \"fox\", \"active\" : true, \"extra\" : { \"foo\" : true } }"
doc = ArangoDB.log_post("#{prefix}-update", api, :body => body)
doc.code.should eq(201)
# remove
doc = ArangoDB.log_delete("#{prefix}-update", api + "/users-1")
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(202)
# now update
doc = ArangoDB.log_patch("#{prefix}-update", api + "/users-1")
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(404)
end
it "update, empty body" do
body = "{ \"username\" : \"users-1\", \"passwd\" : \"fox\", \"active\" : true, \"extra\" : { \"foo\" : true } }"
doc = ArangoDB.log_post("#{prefix}-update", api, :body => body)
# update
body = "{ }"
doc = ArangoDB.log_patch("#{prefix}-update", api + "/users-1", :body => body)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc = ArangoDB.get(api + "/users-1")
doc.code.should eq(200)
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['user'].should eq("users-1")
doc.parsed_response['active'].should eq(true)
doc.parsed_response['extra'].should eq({ "foo" => true })
end
it "update existing user, no passwd" do
body = "{ \"username\" : \"users-1\", \"passwd\" : \"fox\", \"active\" : true, \"extra\" : { \"foo\" : true } }"
doc = ArangoDB.log_post("#{prefix}-update", api, :body => body)
# update
body = "{ \"active\" : false, \"extra\" : { \"foo\" : false } }"
doc = ArangoDB.log_patch("#{prefix}-update", api + "/users-1", :body => body)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc = ArangoDB.get(api + "/users-1")
doc.code.should eq(200)
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['user'].should eq("users-1")
doc.parsed_response['active'].should eq(false)
doc.parsed_response['extra'].should eq({ "foo" => false })
end
it "update existing user" do
body = "{ \"username\" : \"users-1\", \"passwd\" : \"fox\", \"active\" : true, \"extra\" : { \"foo\" : true } }"
doc = ArangoDB.log_post("#{prefix}-update", api, :body => body)
# update
body = "{ \"passwd\" : \"fox2\", \"active\" : false }"
doc = ArangoDB.log_patch("#{prefix}-update", api + "/users-1", :body => body)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc = ArangoDB.get(api + "/users-1")
doc.code.should eq(200)
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['user'].should eq("users-1")
doc.parsed_response['active'].should eq(false)
doc.parsed_response['extra'].should eq({ "foo" => true })
end
end
################################################################################
## removing users
################################################################################
context "removing users" do
it "remove, no user" do
doc = ArangoDB.log_delete("#{prefix}-remove", api)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400)
end
it "remove non-existing user" do
doc = ArangoDB.log_delete("#{prefix}-remove", api + "/users-1")
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(404)
end
it "remove already removed user" do
body = "{ \"username\" : \"users-1\", \"passwd\" : \"fox\", \"active\" : true, \"extra\" : { \"foo\" : true } }"
doc = ArangoDB.log_post("#{prefix}-delete", api, :body => body)
doc.code.should eq(201)
# remove for the first time
doc = ArangoDB.log_delete("#{prefix}-remove", api + "/users-1")
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(202)
# remove again
doc = ArangoDB.log_delete("#{prefix}-remove", api + "/users-1")
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(404)
end
it "remove existing user" do
body = "{ \"username\" : \"users-1\", \"passwd\" : \"fox\", \"active\" : true, \"extra\" : { \"foo\" : true } }"
doc = ArangoDB.log_post("#{prefix}-delete", api, :body => body)
# remove
doc = ArangoDB.log_delete("#{prefix}-remove", api + "/users-1")
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(202)
end
end
################################################################################
## fetching users
################################################################################
context "fetching users" do
it "no user specified" do
doc = ArangoDB.log_get("#{prefix}-fetch", api)
doc.code.should eq(400)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400)
doc.parsed_response['errorNum'].should eq(400)
end
it "fetch non-existing user" do
doc = ArangoDB.log_get("#{prefix}-fetch", api + "/users-16")
doc.code.should eq(404)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(404)
doc.parsed_response['errorNum'].should eq(1703)
end
it "fetch user" do
body = "{ \"username\" : \"users-2\", \"passwd\" : \"fox\", \"active\" : false, \"extra\" : { \"foo\" : true } }"
doc = ArangoDB.log_post("#{prefix}-fetch", api, :body => body)
doc.code.should eq(201)
doc.headers['content-type'].should eq("application/json; charset=utf-8")
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(201)
doc = ArangoDB.get(api + "/users-2")
doc.code.should eq(200)
doc.parsed_response['error'].should eq(false)
doc.parsed_response['code'].should eq(200)
doc.parsed_response['user'].should eq("users-2")
doc.parsed_response['active'].should eq(false)
doc.parsed_response['extra'].should eq({ "foo" => true })
doc.parsed_response.should_not have_key("passwd")
end
end
end
end

View File

@ -21,4 +21,5 @@ rspec --color --format d \
api-explain-spec.rb \
api-cursor-spec.rb \
api-statistics-spec.rb \
api-simple-spec.rb
api-simple-spec.rb \
api-users-spec.rb

View File

@ -214,6 +214,7 @@ SHELL_COMMON = @top_srcdir@/js/common/tests/shell-document.js \
@top_srcdir@/js/common/tests/shell-compactor.js \
@top_srcdir@/js/common/tests/shell-simple-query.js \
@top_srcdir@/js/common/tests/shell-statement.js \
@top_srcdir@/js/common/tests/shell-crypto.js \
@top_srcdir@/js/common/tests/shell-users.js \
@top_srcdir@/js/common/tests/shell-index.js \
@top_srcdir@/js/common/tests/shell-index-geo.js \

View File

@ -0,0 +1,409 @@
////////////////////////////////////////////////////////////////////////////////
/// @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 Achim Brandt
/// @author Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var arangodb = require("org/arangodb");
var actions = require("org/arangodb/actions");
var users = require("org/arangodb/users");
var ArangoError = require("org/arangodb/arango-error").ArangoError;
// -----------------------------------------------------------------------------
// --SECTION-- private functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoAPI
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief fetch a user
///
/// @RESTHEADER{GET /_api/user,fetches a user}
///
/// @REST{GET /_api/user/@FA{username}}
///
/// Fetches data about the specified user.
///
/// The call will return a JSON document with at least the following attributes
/// on success:
///
/// - @LIT{username}: The name of the user as a string.
///
/// - @LIT{active}: an optional flag that specifies whether the user is active.
///
/// - @LIT{extra}: an optional JSON object with arbitrary extra data about the
/// user.
////////////////////////////////////////////////////////////////////////////////
function GET_api_user (req, res) {
if (req.suffix.length != 1) {
actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER);
return;
}
var username = decodeURIComponent(req.suffix[0]);
try {
var result = users.document(username);
actions.resultOk(req, res, actions.HTTP_OK, result)
}
catch (err) {
if (err.errorNum === arangodb.errors.ERROR_USER_NOT_FOUND.code) {
actions.resultNotFound(req, res, arangodb.errors.ERROR_USER_NOT_FOUND.code);
}
else {
throw err;
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a new user
///
/// @RESTHEADER{POST /_api/user,creates user}
///
/// @REST{POST /_api/user}
///
/// The following data need to be passed in a JSON representation in the body of
/// the POST request:
///
/// - @LIT{username}: The name of the user as a string. This is mandatory.
///
/// - @LIT{passwd}: The user password as a string. If no password is specified,
/// the empty string will be used.
///
/// - @LIT{active}: an optional flag that specifies whether the user is active.
/// If not specified, this will default to @LIT{true}.
///
/// - @LIT{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
/// @LIT{HTTP 201}.
///
/// In case of success, the returned JSON object has the following properties:
///
/// - @LIT{error}: boolean flag to indicate that an error occurred (@LIT{false}
/// in this case)
///
/// - @LIT{code}: the HTTP status code
///
/// If the JSON representation is malformed or mandatory data is missing from the
/// request, the server will respond with @LIT{HTTP 400}.
///
/// The body of the response will contain a JSON object with additional error
/// details. The object has the following attributes:
///
/// - @LIT{error}: boolean flag to indicate that an error occurred (@LIT{true} in this case)
///
/// - @LIT{code}: the HTTP status code
///
/// - @LIT{errorNum}: the server error number
///
/// - @LIT{errorMessage}: a descriptive error message
////////////////////////////////////////////////////////////////////////////////
function POST_api_user (req, res) {
var json = actions.getJsonBody(req, res, actions.HTTP_BAD);
if (json === undefined) {
return;
}
var result = users.save(json.username, json.passwd, json.active, json.extra);
users.reload();
actions.resultOk(req, res, actions.HTTP_CREATED, { });
}
////////////////////////////////////////////////////////////////////////////////
/// @brief replace an existing user
///
/// @RESTHEADER{PUT /_api/user,replaces user}
///
/// @REST{PUT /_api/user/@FA{username}}
///
/// Replaces the data of an existing user. The name of an existing user must
/// be specified in @FA{username}.
///
/// The following data can to be passed in a JSON representation in the body of
/// the POST request:
///
/// - @LIT{passwd}: The user password as a string. Specifying a password is
/// mandatory, but the empty string is allowed for passwords.
///
/// - @LIT{active}: an optional flag that specifies whether the user is active.
/// If not specified, this will default to @LIT{true}.
///
/// - @LIT{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
/// @LIT{HTTP 200}.
///
/// In case of success, the returned JSON object has the following properties:
///
/// - @LIT{error}: boolean flag to indicate that an error occurred (@LIT{false}
/// in this case)
///
/// - @LIT{code}: the HTTP status code
///
/// If the JSON representation is malformed or mandatory data is missing from the
/// request, the server will respond with @LIT{HTTP 400}. If the specified user
/// does not exist, the server will respond with @LIT{HTTP 404}.
///
/// The body of the response will contain a JSON object with additional error
/// details. The object has the following attributes:
///
/// - @LIT{error}: boolean flag to indicate that an error occurred (@LIT{true} in this case)
///
/// - @LIT{code}: the HTTP status code
///
/// - @LIT{errorNum}: the server error number
///
/// - @LIT{errorMessage}: a descriptive error message
////////////////////////////////////////////////////////////////////////////////
function PUT_api_user (req, res) {
if (req.suffix.length != 1) {
actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER);
return;
}
var username = decodeURIComponent(req.suffix[0]);
var json = actions.getJsonBody(req, res, actions.HTTP_BAD);
if (json === undefined) {
return;
}
try {
var result = users.replace(username, json.passwd, json.active, json.extra);
users.reload();
actions.resultOk(req, res, actions.HTTP_OK, { });
}
catch (err) {
if (err.errorNum === arangodb.errors.ERROR_USER_NOT_FOUND.code) {
actions.resultNotFound(req, res, arangodb.errors.ERROR_USER_NOT_FOUND.code);
}
else {
throw err;
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief partially update an existing user
///
/// @RESTHEADER{PATCH /_api/user,updates user}
///
/// @REST{PATCH /_api/user/@FA{username}}
///
/// Partially updates the data of an existing user. The name of an existing user
/// must be specified in @FA{username}.
///
/// The following data can be passed in a JSON representation in the body of
/// the POST request:
///
/// - @LIT{passwd}: The user password as a string. Specifying a password is
/// optional. If not specified, the previously existing value will not be
/// modified.
///
/// - @LIT{active}: an optional flag that specifies whether the user is active.
/// If not specified, the previously existing value will not be modified.
///
/// - @LIT{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
/// @LIT{HTTP 200}.
///
/// In case of success, the returned JSON object has the following properties:
///
/// - @LIT{error}: boolean flag to indicate that an error occurred (@LIT{false}
/// in this case)
///
/// - @LIT{code}: the HTTP status code
///
/// If the JSON representation is malformed or mandatory data is missing from the
/// request, the server will respond with @LIT{HTTP 400}. If the specified user
/// does not exist, the server will respond with @LIT{HTTP 404}.
///
/// The body of the response will contain a JSON object with additional error
/// details. The object has the following attributes:
///
/// - @LIT{error}: boolean flag to indicate that an error occurred (@LIT{true} in this case)
///
/// - @LIT{code}: the HTTP status code
///
/// - @LIT{errorNum}: the server error number
///
/// - @LIT{errorMessage}: a descriptive error message
////////////////////////////////////////////////////////////////////////////////
function PATCH_api_user (req, res) {
if (req.suffix.length != 1) {
actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER);
return;
}
var username = decodeURIComponent(req.suffix[0]);
var json = actions.getJsonBody(req, res, actions.HTTP_BAD);
if (json === undefined) {
return;
}
try {
var result = users.update(username, json.passwd, json.active, json.extra);
users.reload();
actions.resultOk(req, res, actions.HTTP_OK, { });
}
catch (err) {
if (err.errorNum === arangodb.errors.ERROR_USER_NOT_FOUND.code) {
actions.resultNotFound(req, res, arangodb.errors.ERROR_USER_NOT_FOUND.code);
}
else {
throw err;
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief remove an existing user
///
/// @RESTHEADER{DELETE /_api/user,removes a user}
///
/// @REST{DELETE /_api/user/@FA{username}}
///
/// Removes an existing user, identified by @FA{username}.
///
/// If the user can be removed by the server, the server will respond with
/// @LIT{HTTP 202}.
///
/// In case of success, the returned JSON object has the following properties:
///
/// - @LIT{error}: boolean flag to indicate that an error occurred (@LIT{false}
/// in this case)
///
/// - @LIT{code}: the HTTP status code
///
/// If the JSON representation is malformed or mandatory data is missing from the
/// request, the server will respond with @LIT{HTTP 400}. If the specified user
/// does not exist, the server will respond with @LIT{HTTP 404}.
///
/// The body of the response will contain a JSON object with additional error
/// details. The object has the following attributes:
///
/// - @LIT{error}: boolean flag to indicate that an error occurred (@LIT{true} in this case)
///
/// - @LIT{code}: the HTTP status code
///
/// - @LIT{errorNum}: the server error number
///
/// - @LIT{errorMessage}: a descriptive error message
////////////////////////////////////////////////////////////////////////////////
function DELETE_api_user (req, res) {
if (req.suffix.length != 1) {
actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER);
return;
}
var username = decodeURIComponent(req.suffix[0]);
try {
var result = users.remove(username);
users.reload();
actions.resultOk(req, res, actions.HTTP_ACCEPTED, { });
}
catch (err) {
if (err.errorNum === arangodb.errors.ERROR_USER_NOT_FOUND.code) {
actions.resultNotFound(req, res, arangodb.errors.ERROR_USER_NOT_FOUND.code);
}
else {
throw err;
}
}
}
// -----------------------------------------------------------------------------
// --SECTION-- initialiser
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief user actions gateway
////////////////////////////////////////////////////////////////////////////////
actions.defineHttp({
url : "_api/user",
context : "api",
callback : function (req, res) {
try {
switch (req.requestType) {
case actions.GET:
GET_api_user(req, res);
break;
case actions.POST:
POST_api_user(req, res);
break;
case actions.PUT:
PUT_api_user(req, res);
break;
case actions.PATCH:
PATCH_api_user(req, res);
break;
case actions.DELETE:
DELETE_api_user(req, res);
break;
default:
actions.resultUnsupported(req, res);
}
}
catch (err) {
actions.resultException(req, res, err);
}
}
});
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
// End:

View File

@ -1,12 +1,13 @@
/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true, nonpropdel: true, proto: true */
/*global require, module, Module, FS_MOVE, FS_REMOVE, FS_EXISTS, FS_IS_DIRECTORY, FS_LIST_TREE,
SYS_EXECUTE, SYS_LOAD, SYS_LOG, SYS_LOG_LEVEL, SYS_OUTPUT, SYS_PROCESS_STAT, SYS_READ,
SYS_SPRINTF, SYS_TIME, SYS_START_PAGER, SYS_STOP_PAGER, SYS_SHA256, SYS_WAIT, SYS_GETLINE,
SYS_PARSE, SYS_SAVE, SYS_IMPORT_CSV_FILE, SYS_IMPORT_JSON_FILE, SYS_PROCESS_CSV_FILE,
SYS_PROCESS_JSON_FILE, ARANGO_QUIET, MODULES_PATH, COLORS, COLOR_OUTPUT, COLOR_OUTPUT_RESET,
COLOR_BRIGHT, COLOR_BLACK, COLOR_BOLD_BLACK, COLOR_BLINK, COLOR_BLUE, COLOR_BOLD_BLUE,
COLOR_BOLD_GREEN, COLOR_RED, COLOR_BOLD_RED, COLOR_GREEN, COLOR_WHITE, COLOR_BOLD_WHITE,
COLOR_YELLOW, COLOR_BOLD_YELLOW, PRETTY_PRINT, VALGRIND, HAS_ICU, VERSION, UPGRADE */
SYS_EXECUTE, SYS_LOAD, SYS_LOG, SYS_LOG_LEVEL, SYS_MD5, SYS_OUTPUT, SYS_PROCESS_STAT, SYS_RAND,
SYS_READ, SYS_SPRINTF, SYS_TIME, SYS_START_PAGER, SYS_STOP_PAGER, SYS_SHA256, SYS_WAIT,
SYS_GETLINE, SYS_PARSE, SYS_SAVE, SYS_IMPORT_CSV_FILE, SYS_IMPORT_JSON_FILE,
SYS_PROCESS_CSV_FILE, SYS_PROCESS_JSON_FILE, ARANGO_QUIET, MODULES_PATH, COLORS, COLOR_OUTPUT,
COLOR_OUTPUT_RESET, COLOR_BRIGHT, COLOR_BLACK, COLOR_BOLD_BLACK, COLOR_BLINK, COLOR_BLUE,
COLOR_BOLD_BLUE, COLOR_BOLD_GREEN, COLOR_RED, COLOR_BOLD_RED, COLOR_GREEN, COLOR_WHITE,
COLOR_BOLD_WHITE, COLOR_YELLOW, COLOR_BOLD_YELLOW, PRETTY_PRINT, VALGRIND, HAS_ICU, VERSION,
UPGRADE */
////////////////////////////////////////////////////////////////////////////////
/// @brief module "internal"
@ -76,6 +77,11 @@
internal.logLevel = SYS_LOG_LEVEL;
delete SYS_LOG_LEVEL;
}
if (typeof SYS_MD5 !== "undefined") {
internal.md5 = SYS_MD5;
delete SYS_MD5;
}
if (typeof SYS_OUTPUT !== "undefined") {
internal.stdOutput = SYS_OUTPUT;
@ -92,6 +98,11 @@
internal.processStat = SYS_PROCESS_STAT;
delete SYS_PROCESS_STAT;
}
if (typeof SYS_RAND !== "undefined") {
internal.rand = SYS_RAND;
delete SYS_RAND;
}
if (typeof SYS_READ !== "undefined") {
internal.read = SYS_READ;
@ -798,22 +809,6 @@
}
};
////////////////////////////////////////////////////////////////////////////////
/// @brief encode password using SHA256
////////////////////////////////////////////////////////////////////////////////
internal.encodePassword = function (password) {
var salt;
var encoded;
salt = internal.sha256("time:" + internal.time());
salt = salt.substr(0,8);
encoded = "$1$" + salt + "$" + internal.sha256(salt + password);
return encoded;
};
////////////////////////////////////////////////////////////////////////////////
/// @brief extends a prototype
////////////////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,101 @@
/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
/*global require, exports */
////////////////////////////////////////////////////////////////////////////////
/// @brief Some crypto functions
///
/// @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 2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var internal = require("internal");
// -----------------------------------------------------------------------------
// --SECTION-- RANDOM
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// --SECTION-- public methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup Random
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief generate a random number
///
/// the value returned might be positive or negative or even 0.
/// if random number generation fails, then undefined is returned
////////////////////////////////////////////////////////////////////////////////
exports.rand = function (value) {
return internal.rand();
};
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- HASHES
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// --SECTION-- public methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup Hashes
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief apply an MD5 hash
////////////////////////////////////////////////////////////////////////////////
exports.md5 = function (value) {
return internal.md5(value);
};
////////////////////////////////////////////////////////////////////////////////
/// @brief apply an SHA 256 hash
////////////////////////////////////////////////////////////////////////////////
exports.sha256 = function (value) {
return internal.sha256(value);
};
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @}\\|/\\*jslint"
// End:

View File

@ -28,11 +28,11 @@
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var internal = require("internal"); // OK: encodePassword, reloadAuth
var internal = require("internal"); // OK: reloadAuth, time
var encodePassword = internal.encodePassword;
var reloadAuth = internal.reloadAuth;
var arangodb = require("org/arangodb");
var crypto = require("org/arangodb/crypto");
var db = arangodb.db;
var ArangoError = require("org/arangodb/arango-error").ArangoError;
@ -49,6 +49,30 @@ var ArangoError = require("org/arangodb/arango-error").ArangoError;
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @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
////////////////////////////////////////////////////////////////////////////////
@ -112,14 +136,16 @@ var getStorage = function () {
/// @fn JSF_saveUser
/// @brief create a new user
///
/// @FUN{@FA{users}.save(@FA{username}, @FA{passwd})}
/// @FUN{@FA{users}.save(@FA{username}, @FA{passwd}, @FA{active}, @FA{extra})}
///
/// This will create a new ArangoDB user.
/// The username must be a string and contain only the letters a-z (lower or
/// upper case), the digits 0-9, the dash or the underscore symbol.
/// This will create a new ArangoDB user. The username must be specified 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
@ -135,7 +161,7 @@ var getStorage = function () {
/// @TINYEXAMPLE{user-save,saving a new user}
////////////////////////////////////////////////////////////////////////////////
exports.save = function (username, passwd) {
exports.save = function (username, passwd, active, extra) {
if (passwd === null || passwd === undefined) {
passwd = "";
}
@ -147,9 +173,24 @@ exports.save = function (username, passwd) {
var users = getStorage();
var user = users.firstExample({ user: username });
if (active === undefined || active === null) {
// this is the default value for active
active = true;
}
if (user === null) {
var hash = encodePassword(passwd);
return users.save({ user: username, password: hash, active: true });
var data = {
user: username,
password: hash,
active: active
};
if (extra !== undefined) {
data.extra = extra;
}
return users.save(data);
}
var err = new ArangoError();
@ -161,19 +202,91 @@ exports.save = function (username, passwd) {
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_replaceUser
/// @brief update an existing user
/// @brief replace an existing user
///
/// @FUN{@FA{users}.replace(@FA{username}, @FA{passwd})}
/// @FUN{@FA{users}.replace(@FA{username}, @FA{passwd}, @FA{active}, @FA{extra})}
///
/// This will update an existing ArangoDB user with a new password.
/// This will look up an existing ArangoDB user and replace its user data.
///
/// The username must be a string and contain only the letters a-z (lower or
/// upper case), the digits 0-9, the dash or the underscore symbol. The user
/// must already exist in the database.
/// The username must be specified, 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
///
/// @TINYEXAMPLE{user-replace,replacing an existing user}
////////////////////////////////////////////////////////////////////////////////
exports.replace = function (username, passwd, active, extra) {
if (passwd === null || passwd === undefined) {
passwd = "";
}
// validate input
validateName(username);
validatePassword(passwd);
var users = getStorage();
var user = users.firstExample({ user: username });
if (active === undefined || active === null) {
// this is the default
active = true;
}
if (user === 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: username,
password: hash,
active: active
};
if (extra !== undefined) {
data.extra = extra;
}
return users.replace(user, data);
};
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_updateUser
/// @brief update an existing user
///
/// @FUN{@FA{users}.update(@FA{username}, @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 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.
@ -187,20 +300,17 @@ exports.save = function (username, passwd) {
///
/// @TINYEXAMPLE{user-replace,replacing an existing user}
////////////////////////////////////////////////////////////////////////////////
exports.replace =
exports.update = function (username, passwd) {
if (passwd === null || passwd === undefined) {
passwd = "";
}
exports.update = function (username, passwd, active, extra) {
// validate input
validateName(username);
validatePassword(passwd);
if (passwd !== undefined) {
validatePassword(passwd);
}
var users = getStorage();
var user = users.firstExample({ user: username });
if (user === null) {
var err = new ArangoError();
err.errorNum = arangodb.errors.ERROR_USER_NOT_FOUND.code;
@ -209,10 +319,20 @@ exports.update = function (username, passwd) {
throw err;
}
var hash = encodePassword(passwd);
var data = user.shallowCopy;
data.password = hash;
return users.replace(user, data);
if (passwd !== undefined) {
var hash = encodePassword(passwd);
data.password = hash;
}
if (active !== undefined && active !== null) {
data.active = active;
}
if (extra !== undefined) {
data.extra = extra;
}
return users.update(user, data);
};
////////////////////////////////////////////////////////////////////////////////
@ -223,9 +343,8 @@ exports.update = function (username, passwd) {
///
/// Removes an existing ArangoDB user from the database.
///
/// The username must be a string and contain only the letters a-z (lower or
/// upper case), the digits 0-9, the dash or the underscore symbol. The user
/// must already exist in the database.
/// The username must be a string and the specified user must exist in the
/// database.
///
/// This method will fail if the user cannot be found in the database.
///
@ -257,6 +376,39 @@ exports.remove = function (username) {
return users.remove(user._id);
};
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_documentUser
/// @brief get an existing user
///
/// @FUN{@FA{users}.document(@FA{username})}
///
/// Fetches an existing ArangoDB user from 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
////////////////////////////////////////////////////////////////////////////////
exports.document = function (username) {
// validate name
validateName(username);
var users = getStorage();
var user = users.firstExample({ user: username });
if (user === 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;
}
delete user.passwd;
return user;
};
////////////////////////////////////////////////////////////////////////////////
/// @brief reloads the user authentication data
///

View File

@ -0,0 +1,139 @@
////////////////////////////////////////////////////////////////////////////////
/// @brief test the crypto interface
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-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 2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var jsunity = require("jsunity");
var crypto = require("org/arangodb/crypto");
// -----------------------------------------------------------------------------
// --SECTION-- crypto methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
function CryptoSuite () {
return {
////////////////////////////////////////////////////////////////////////////////
/// @brief test md5, invalid values
////////////////////////////////////////////////////////////////////////////////
testMd5Invalid : function () {
[ undefined, null, true, false, 0, 1, -1, 32.5, [ ], { } ].forEach(function (value) {
try {
crypto.md5(value);
fail();
}
catch (err) {
}
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test md5
////////////////////////////////////////////////////////////////////////////////
testMd5 : function () {
var data = [
[ "", "d41d8cd98f00b204e9800998ecf8427e" ],
[ " ", "7215ee9c7d9dc229d2921a40e899ec5f" ],
[ "arangodb", "335889e69e03cefd041babef1b02a0b8" ],
[ "Arangodb", "0862bc79ec789143f75e3282df98c8f4" ],
[ "ArangoDB is a database", "b88ddc26cfa3a652fdd8bf8e8c069540" ]
];
data.forEach(function (value) {
assertEqual(value[1], crypto.md5(value[0]));
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test sha256, invalid values
////////////////////////////////////////////////////////////////////////////////
testSha256Invalid : function () {
[ undefined, null, true, false, 0, 1, -1, 32.5, [ ], { } ].forEach(function (value) {
try {
crypto.sha256(value);
fail();
}
catch (err) {
}
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test sha256
////////////////////////////////////////////////////////////////////////////////
testSha256 : function () {
var data = [
[ "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ],
[ " ", "36a9e7f1c95b82ffb99743e0c5c4ce95d83c9a430aac59f84ef3cbfab6145068"],
[ "arangodb", "d0a274654772fa104df32ff457ff0a432f2dfad18e7c3146fab3c807f2ed86e5" ],
[ "Arangodb", "38579e73fd7435de7db93028a1b340be77445b46c94dff013c05c696ccae259c" ],
[ "ArangoDB is a database", "00231e8f9c0a617426ae51e4e230a1b25f6d5b82c10fccc835b514142f235f31" ]
];
data.forEach(function (value) {
assertEqual(value[1], crypto.sha256(value[0]));
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test random
////////////////////////////////////////////////////////////////////////////////
testRandom : function () {
var i;
for (i = 0; i < 100; ++i) {
assertTrue(crypto.rand() != 0);
}
}
};
}
// -----------------------------------------------------------------------------
// --SECTION-- main
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief executes the test suite
////////////////////////////////////////////////////////////////////////////////
jsunity.run(CryptoSuite);
return jsunity.done();
// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
// End:

View File

@ -45,6 +45,7 @@
(function() {
var internal = require("internal");
var console = require("console");
var userManager = require("org/arangodb/users");
var db = internal.db;
// path to the VERSION file
@ -147,7 +148,7 @@
if (users.count() === 0) {
// only add account if user has not created his/her own accounts already
users.save({ user: "root", password: internal.encodePassword(""), active: true });
userManager.save("root", "", true);
}
return true;

View File

@ -31,6 +31,7 @@
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <Basics/RandomGenerator.h>
#include <Basics/StringUtils.h>
@ -130,7 +131,6 @@ namespace triagens {
}
void sslSHA256 (char const* inputStr, char*& outputStr, size_t& outputLen) {
sslSHA256(inputStr, strlen(inputStr), outputStr, outputLen);
}
@ -180,9 +180,8 @@ namespace triagens {
void sslBASE64 (char const* inputStr, char*& outputStr, size_t& outputLen) {
sslBASE64(inputStr, strlen(inputStr), outputStr, outputLen);
}
string sslHMAC (char const* key, char const* message, size_t messageLen) {
const EVP_MD * evp_md = EVP_sha256();
unsigned char* md = (unsigned char*) TRI_SystemAllocate(EVP_MAX_MD_SIZE + 1, false);
@ -212,7 +211,30 @@ namespace triagens {
return false;
}
int sslRand (uint64_t* value) {
if (! RAND_pseudo_bytes((unsigned char *) value, sizeof(uint64_t))) {
return 1;
}
return 0;
}
int sslRand (int64_t* value) {
if (! RAND_pseudo_bytes((unsigned char *) value, sizeof(int64_t))) {
return 1;
}
return 0;
}
int sslRand (int32_t* value) {
if (! RAND_pseudo_bytes((unsigned char *) value, sizeof(int32_t))) {
return 1;
}
return 0;
}
void salt64 (uint64_t& result) {
string salt = SaltGenerator.random(8);

View File

@ -99,12 +99,12 @@ namespace triagens {
//////////////////////////////////////////////////////////////////////////
void sslHEX (char const* inputStr, char*& outputStr, size_t& outputLen);
//////////////////////////////////////////////////////////////////////////
/// @brief BASE64
//////////////////////////////////////////////////////////////////////////
void sslBAE64 (char const* inputStr, const size_t length, char*& outputStr, size_t& outputLen);
void sslBASE64 (char const* inputStr, const size_t length, char*& outputStr, size_t& outputLen);
//////////////////////////////////////////////////////////////////////////
/// @brief BASE64
@ -123,12 +123,36 @@ namespace triagens {
//////////////////////////////////////////////////////////////////////////
bool verifyHMAC (char const* challenge, char const* secret, size_t secretLen, char const* response, size_t responseLen);
//////////////////////////////////////////////////////////////////////////
/// @brief generate a random number using OpenSsl
///
/// will return 0 on success, and != 0 on error
//////////////////////////////////////////////////////////////////////////
int sslRand (uint64_t*);
//////////////////////////////////////////////////////////////////////////
/// @brief generate a random number using OpenSsl
///
/// will return 0 on success, and != 0 on error
//////////////////////////////////////////////////////////////////////////
int sslRand (int64_t*);
//////////////////////////////////////////////////////////////////////////
/// @brief generate a random number using OpenSsl
///
/// will return 0 on success, and != 0 on error
//////////////////////////////////////////////////////////////////////////
int sslRand (int32_t*);
//////////////////////////////////////////////////////////////////////////
/// @brief salt
//////////////////////////////////////////////////////////////////////////
void salt64 (uint64_t& saltInt);
void salt64 (uint64_t& saltInt);
//////////////////////////////////////////////////////////////////////////
/// @brief salt

View File

@ -619,6 +619,46 @@ static v8::Handle<v8::Value> JS_LogLevel (v8::Arguments const& argv) {
return scope.Close(v8::String::New(TRI_LogLevelLogging()));
}
////////////////////////////////////////////////////////////////////////////////
/// @brief md5 sum
///
/// @FUN{internal.md5(@FA{text})}
///
/// Computes an md5 for the @FA{text}.
////////////////////////////////////////////////////////////////////////////////
static v8::Handle<v8::Value> JS_Md5 (v8::Arguments const& argv) {
v8::HandleScope scope;
// extract arguments
if (argv.Length() != 1 || ! argv[0]->IsString()) {
return scope.Close(v8::ThrowException(v8::String::New("usage: md5(<text>)")));
}
string key = TRI_ObjectToString(argv[0]);
// create md5
char* hash = 0;
size_t hashLen;
SslInterface::sslMD5(key.c_str(), key.size(), hash, hashLen);
// as hex
char* hex = 0;
size_t hexLen;
SslInterface::sslHEX(hash, hashLen, hex, hexLen);
delete[] hash;
// and return
v8::Handle<v8::String> hashStr = v8::String::New(hex, hexLen);
delete[] hex;
return scope.Close(hashStr);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief renames a file
///
@ -742,6 +782,46 @@ static v8::Handle<v8::Value> JS_ProcessStat (v8::Arguments const& argv) {
return scope.Close(result);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief generate a random number using OpenSSL
///
/// @FUN{internal.rand()}
///
/// Generates a random number
////////////////////////////////////////////////////////////////////////////////
static v8::Handle<v8::Value> JS_Rand (v8::Arguments const& argv) {
v8::HandleScope scope;
// check arguments
if (argv.Length() != 0) {
return scope.Close(v8::ThrowException(v8::String::New("usage: rand()")));
}
int iterations = 0;
while (iterations++ < 5) {
int32_t value;
int result = SslInterface::sslRand(&value);
if (result != 0) {
// error
break;
}
// no error, now check what random number was produced
if (value != 0) {
// a number != 0 was produced. that is sufficient
return scope.Close(v8::Number::New(value));
}
// we don't want to return 0 as the result, so we try again
}
// we failed to produce a valid random number
return scope.Close(v8::Undefined());
}
////////////////////////////////////////////////////////////////////////////////
/// @brief reads in a file
///
@ -962,14 +1042,14 @@ static v8::Handle<v8::Value> JS_SPrintF (v8::Arguments const& argv) {
///
/// @FUN{internal.sha256(@FA{text})}
///
/// Computes a sha256 for the @FA{text}.
/// Computes an sha256 for the @FA{text}.
////////////////////////////////////////////////////////////////////////////////
static v8::Handle<v8::Value> JS_Sha256 (v8::Arguments const& argv) {
v8::HandleScope scope;
// extract arguments
if (argv.Length() != 1) {
if (argv.Length() != 1 || ! argv[0]->IsString()) {
return scope.Close(v8::ThrowException(v8::String::New("usage: sha256(<text>)")));
}
@ -1398,9 +1478,11 @@ void TRI_InitV8Utils (v8::Handle<v8::Context> context, string const& path) {
TRI_AddGlobalFunctionVocbase(context, "SYS_LOAD", JS_Load);
TRI_AddGlobalFunctionVocbase(context, "SYS_LOG", JS_Log);
TRI_AddGlobalFunctionVocbase(context, "SYS_LOG_LEVEL", JS_LogLevel);
TRI_AddGlobalFunctionVocbase(context, "SYS_MD5", JS_Md5);
TRI_AddGlobalFunctionVocbase(context, "SYS_OUTPUT", JS_Output);
TRI_AddGlobalFunctionVocbase(context, "SYS_PARSE", JS_Parse);
TRI_AddGlobalFunctionVocbase(context, "SYS_PROCESS_STAT", JS_ProcessStat);
TRI_AddGlobalFunctionVocbase(context, "SYS_RAND", JS_Rand);
TRI_AddGlobalFunctionVocbase(context, "SYS_READ", JS_Read);
TRI_AddGlobalFunctionVocbase(context, "SYS_SAVE", JS_Save);
TRI_AddGlobalFunctionVocbase(context, "SYS_SHA256", JS_Sha256);