Skip to content
This repository was archived by the owner on Oct 5, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions app/templates/node-server/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
98 changes: 76 additions & 22 deletions app/templates/node-server/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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');
Expand All @@ -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,
Expand All @@ -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');
});
});

Expand All @@ -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
}

Expand Down
88 changes: 88 additions & 0 deletions app/templates/rest-api/ext/profile.xqy
Original file line number Diff line number Diff line change
@@ -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"' }
};
71 changes: 71 additions & 0 deletions app/templates/src/lib/user-model.xqy
Original file line number Diff line number Diff line change
@@ -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
<jbasic:json type="object"/>
};

declare function user:read($uri as xs:string) as element(jbasic:json) {
let $doc := fn:doc($uri)
return
user:convert($doc)
};

declare function user:save($uri as xs:string, $profile as element(jbasic:json)) {
let $profile :=
if ($is-ml8) then
xdmp:to-json(json:transform-to-json($profile))/node()
else
$profile
return
xdmp:document-insert($uri, $profile, (xdmp:default-permissions(), xdmp:document-get-permissions($uri)), (xdmp:default-collections(), 'users', xdmp:document-get-collections($uri)))
};
21 changes: 11 additions & 10 deletions app/templates/ui/app/user/profile.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,18 @@
_.pull(ctrl.user.emails, '');
}

mlRest.updateDocument({
user: {
'fullname': ctrl.user.fullname,
'emails': ctrl.user.emails
ctrl.message = undefined;
mlRest.callExtension('profile', {
method: 'PUT',
data: {
user: {
fullname: ctrl.user.fullname,
emails: ctrl.user.emails
}
},
headers: {
'Content-Type': 'application/json'
}
}, {
format: 'json',
uri: '/api/users/' + ctrl.user.name + '.json'
// TODO: add read/update permissions here like this:
// 'perm:sample-role': 'read',
// 'perm:sample-role': 'update'
}).then(function(data) {
toast.success('Submitted');
$state.go('root');
Expand Down
5 changes: 5 additions & 0 deletions app/templates/ui/app/user/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,10 @@ <h2 class="col-md-10">Edit your profile</h2>
<button class="btn btn-primary" ng-click="ctrl.submit(profileForm)">Submit</button>
</div>
</div>
<div class="row">
<p class="col-sm-offset-2 col-sm-8 text-info" ng-if="ctrl.message">
<alert type="info"><strong>{{ctrl.message}}</strong></alert>
</p>
</div>
</form>
</div>
Loading