diff --git a/app/templates/node-server/proxy.js b/app/templates/node-server/proxy.js
index 7c2f2a6d..0f5adbc2 100644
--- a/app/templates/node-server/proxy.js
+++ b/app/templates/node-server/proxy.js
@@ -33,11 +33,11 @@ router.put('*', function(req, res) {
// For PUT requests, require authentication
if (req.session.user === undefined) {
res.status(401).send('Unauthorized');
- } else if (req.path === '/v1/documents' &&
- req.query.uri.match('/api/users/') &&
- req.query.uri.match(new RegExp('/api/users/[^(' + req.session.user.name + ')]+.json'))) {
- // The user is try to PUT to a profile document other than his/her own. Not allowed.
- res.status(403).send('Forbidden');
+ // } else if (req.path === '/v1/documents' &&
+ // req.query.uri.match('/api/users/') &&
+ // req.query.uri.match(new RegExp('/api/users/[^(' + req.session.user.name + ')]+.json'))) {
+ // // The user is try to PUT to a profile document other than his/her own. Not allowed.
+ // res.status(403).send('Forbidden');
} else {
if (req.path === '/v1/documents' && req.query.uri.match('/users/')) {
// TODO: The user is updating the profile. Update the session info.
diff --git a/app/templates/node-server/routes.js b/app/templates/node-server/routes.js
index f551e36c..1e79dc91 100644
--- a/app/templates/node-server/routes.js
+++ b/app/templates/node-server/routes.js
@@ -17,47 +17,77 @@ var options = {
};
// [GJo] (#31) Moved bodyParsing inside routing, otherwise it might try to parse uploaded binaries as json..
-router.use(bodyParser.urlencoded({extended: true}));
+router.use(bodyParser.urlencoded({ extended: true }));
router.use(bodyParser.json());
router.get('/user/status', function(req, res) {
var headers = req.headers;
noCache(res);
+
if (req.session.user === undefined) {
- res.send({authenticated: false});
+ res.send({ authenticated: false });
} else {
delete headers['content-length'];
+
+ var username = req.session.user.name;
+ var password = req.session.user.password;
+
var status = http.get({
hostname: options.mlHost,
port: options.mlHttpPort,
- path: '/v1/documents?uri=/api/users/' + req.session.user.name + '.json',
+ path: '/v1/documents?uri=/users/' + username + '.json',
headers: headers,
- auth: req.session.user.name + ':' + req.session.user.password
+ auth: username + ':' + password
}, function(response) {
+ if (response.statusCode === 401) {
+ res.statusCode = 401;
+ res.send('Unauthenticated');
+ } else if (response.statusCode === 404) {
+ // authentication successful, but no profile defined
+ req.session.user = {
+ name: username,
+ password: password
+ };
+ res.status(200).send({
+ authenticated: true,
+ username: username
+ });
+ } else {
+ console.log('code: ' + response.statusCode);
if (response.statusCode === 200) {
+ // authentication successful, remember the username
+ req.session.user = {
+ name: username,
+ password: password
+ };
response.on('data', function(chunk) {
+ console.log('chunk: ' + chunk);
+
var json = JSON.parse(chunk);
+ req.session.user.profile = {};
+
+ if (json.user !== undefined) {
+ req.session.user.profile.fullname = json.user.fullname;
+ req.session.user.profile.emails = json.user.emails;
+ }
+ req.session.user.profile.webroles = json.webroles;
+
if (json.user !== undefined) {
res.status(200).send({
authenticated: true,
- username: req.session.user.name,
+ username: username,
profile: json.user
});
} else {
console.log('did not find chunk.user');
}
});
- } else if (response.statusCode === 404) {
- //no profile yet for user
- res.status(200).send({
- authenticated: true,
- username: req.session.user.name,
- profile: {}
- });
} else {
- res.send({authenticated: false});
+ res.statusCode = response.statusCode;
+ res.send(response.statusMessage);
}
- });
+ }
+ });
status.on('error', function(e) {
console.log(JSON.stringify(e));
@@ -79,13 +109,16 @@ router.post('/user/login', function(req, res) {
// remove content length so ML doesn't wait for request body
// that isn't being passed.
delete headers['content-length'];
- var login = http.get({
+ var login = http.request({
+ method: 'POST',
hostname: options.mlHost,
port: options.mlHttpPort,
- path: '/v1/documents?uri=/api/users/' + username + '.json',
- headers: headers,
+ // path: '/v1/documents?uri=/api/users/' + username + '.json',
+ path: '/v1/resources/profile',
+ // headers: headers,
auth: username + ':' + password
}, function(response) {
+ console.log('login response : ' + response);
if (response.statusCode === 401) {
res.statusCode = 401;
res.send('Unauthenticated');
@@ -108,7 +141,17 @@ router.post('/user/login', function(req, res) {
password: password
};
response.on('data', function(chunk) {
+ console.log('chunk: ' + chunk);
+
var json = JSON.parse(chunk);
+ req.session.user.profile = {};
+
+ if (json.user !== undefined) {
+ req.session.user.profile.fullname = json.user.fullname;
+ req.session.user.profile.emails = json.user.emails;
+ }
+ req.session.user.profile.webroles = json.webroles;
+
if (json.user !== undefined) {
res.status(200).send({
authenticated: true,
@@ -126,9 +169,20 @@ router.post('/user/login', function(req, res) {
}
});
+ login.end();
+
+ login.on('socket', function(socket) {
+ socket.setTimeout(10000);
+ socket.on('timeout', function() {
+ console.log('timeout..');
+ login.abort();
+ });
+ });
+
login.on('error', function(e) {
- console.log(JSON.stringify(e));
- console.log('login failed: ' + e.statusCode);
+ console.log('login failed: ' + e);
+ login.abort();
+ res.status(500).send('Login failed');
});
});
@@ -140,9 +194,9 @@ router.get('/user/logout', function(req, res) {
router.get('/*', four0four.notFoundMiddleware);
-function noCache(response){
- response.append('Cache-Control', 'no-cache, must-revalidate');//HTTP 1.1 - must-revalidate
- response.append('Pragma', 'no-cache');//HTTP 1.0
+function noCache(response) {
+ response.append('Cache-Control', 'no-cache, must-revalidate'); //HTTP 1.1 - must-revalidate
+ response.append('Pragma', 'no-cache'); //HTTP 1.0
response.append('Expires', 'Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
}
diff --git a/app/templates/rest-api/ext/profile.xqy b/app/templates/rest-api/ext/profile.xqy
new file mode 100644
index 00000000..c08fe9d8
--- /dev/null
+++ b/app/templates/rest-api/ext/profile.xqy
@@ -0,0 +1,88 @@
+xquery version "1.0-ml";
+
+module namespace profile = "http://marklogic.com/rest-api/resource/profile";
+
+import module namespace json="http://marklogic.com/xdmp/json"
+ at "/MarkLogic/json/json.xqy";
+import module namespace user = "http://marklogic.com/slush/user-model"
+ at "/lib/user-model.xqy";
+
+declare namespace roxy = "http://marklogic.com/roxy";
+
+declare option xdmp:mapping "false";
+
+declare variable $ROLE_READER := "marklogic-slush-reader-role";
+declare variable $ROLE_WRITER := "marklogic-slush-writer-role";
+declare variable $ROLE_ADMIN := "marklogic-slush-admin-role";
+
+(:
+
+ This gets the profile if it exists,
+ and the user's webroles are determined based on the user's system roles for this app
+
+ :)
+declare function profile:post(
+ $context as map:map,
+ $params as map:map,
+ $input as document-node()*
+) as document-node()*
+{
+ map:put($context, "output-types", "application/json"),
+ let $username := xdmp:get-current-user()
+ let $uri := "/users/"||$username||".json"
+ let $profile := fn:doc($uri)
+ let $profile :=
+ if ($profile/element())
+ then json:transform-to-json-object($profile)
+ else if ($profile)
+ then xdmp:from-json($profile)
+ else (
+ let $object := json:object()
+ let $_ := map:put($object, "user", json:object())
+ return $object
+ )
+
+ let $webroles := profile:get-webroles-for-user()
+ let $webroles-array := json:array()
+ let $_ :=
+ for $webrole in $webroles
+ return json:array-push($webroles-array, $webrole)
+
+ let $_ := map:put($profile, "webroles", $webroles-array)
+
+ let $user := map:get($profile, "user")
+
+ return document{ xdmp:to-json($profile) }
+};
+
+(:
+ Rather than expose the system role names, we'll represent them as "webroles" that the UI can use for permission checking.
+:)
+declare private function profile:get-webroles-for-user() {
+(: if (xdmp:get-current-roles() = (xdmp:role("admin"), xdmp:role($ROLE_ADMIN)))
+ then ("admin", "writer", "reader")
+ else if (xdmp:get-current-roles() = xdmp:role($ROLE_WRITER))
+ then ("writer", "reader")
+ else if (xdmp:get-current-roles() = xdmp:role($ROLE_READER))
+ then "reader"
+ else ()
+ :)
+
+ if (xdmp:get-current-roles() = (xdmp:role("admin")))
+ then ("admin", "writer", "reader")
+ else ()
+};
+
+declare function profile:put(
+ $context as map:map,
+ $params as map:map,
+ $input as document-node()*
+) as document-node()?
+{
+ map:put($context, "output-types", "application/json"),
+ let $username := xdmp:get-current-user()
+ let $profile :=
+ user:convert($input)
+ let $_ := user:put($username, $profile)
+ return document{ '"ok"' }
+};
diff --git a/app/templates/src/lib/user-model.xqy b/app/templates/src/lib/user-model.xqy
new file mode 100644
index 00000000..3db0722e
--- /dev/null
+++ b/app/templates/src/lib/user-model.xqy
@@ -0,0 +1,71 @@
+xquery version "1.0-ml";
+
+module namespace user = "http://marklogic.com/slush/user-model";
+
+import module namespace json = "http://marklogic.com/xdmp/json" at "/MarkLogic/json/json.xqy";
+
+declare namespace alert = "http://marklogic.com/xdmp/alert";
+declare namespace jbasic = "http://marklogic.com/xdmp/json/basic";
+
+declare option xdmp:mapping "false";
+
+declare variable $is-ml8 := fn:starts-with(xdmp:version(), "8");
+
+(: access :)
+
+declare function user:replace($profile as element(jbasic:json), $properties as element()*, $new-properties as element()*) as element(jbasic:json) {
+ element { fn:node-name($profile) } {
+ $profile/@*,
+ $profile/(* except $profile/jbasic:user),
+ element jbasic:user {
+ attribute type { "object"},
+ $profile/jbasic:user/(@* except @type),
+
+ $profile/jbasic:user/(* except $properties),
+ $new-properties
+ }
+ }
+};
+
+(: low-level access :)
+
+declare function user:uri($id as xs:string) as xs:string {
+ fn:concat('/users/', $id, '.json')
+};
+
+declare function user:get($id as xs:string) as element(jbasic:json) {
+ let $uri := user:uri($id)
+ return
+ user:read($uri)
+};
+
+declare function user:put($id as xs:string, $profile as element(jbasic:json)) {
+ let $uri := user:uri($id)
+ return
+ user:save($uri, $profile)
+};
+
+declare function user:convert($doc as document-node()?) as element(jbasic:json) {
+ if ($doc/jbasic:json) then
+ $doc/jbasic:json
+ else if ($doc) then
+ json:transform-from-json($doc)
+ else
+
+