Refactored code to work with trails

This commit is contained in:
Tobi Schäfer 2014-12-01 20:45:16 +01:00
parent da65dbbbfd
commit b112395a53
15 changed files with 77 additions and 552 deletions

1
.gitignore vendored
View file

@ -20,3 +20,4 @@ static/www/files
static/www/images
static/www/layout
static/www/node_modules
trails

View file

@ -18,6 +18,7 @@
/**
* @fileoverview Defines the Feature prototype.
* Another trial to implement modular features.
* @deprecated
*/
/**
@ -162,4 +163,4 @@ Feature.getPermission = function(action) {
}
}
return false;
}
}

43
code/Global/Trail.js Normal file
View file

@ -0,0 +1,43 @@
// The Antville Project
// http://code.google.com/p/antville
//
// Copyright 20012014 by the Workers of Antville.
//
// 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.
function Trail(name) {
var Proto = global[name];
if (Proto && Proto instanceof Function) {
return Proto;
}
app.log('Undefined trail ' + name);
return;
}
Trail.invoke = function (name, callback, args) {
var trail = Trail(name);
if (trail) {
callback.apply(trail, args);
}
};
function trail_macro(param, name, macro) {
macro || (macro = 'trail');
var Proto = Trail(name);
var trail = new Proto();
var method = trail[macro + '_macro'];
if (method && method instanceof Function) {
method.call(trail, param);
}
return;
}

View file

@ -43,7 +43,7 @@
<div class='uk-form-row'>
<fieldset>
<legend><% gettext Connections %></legend>
<% feature connect prefix='' suffix='' %>
<% trail Connect prefix='' suffix='' %>
</fieldset>
</div>
<div class='uk-form-row'>

View file

@ -90,7 +90,7 @@
<textarea cols="31" rows="10" class="formText" wrap="virtual"
name="text"><% request.text encoding="form" %></textarea>
</p>
<% feature recaptcha %>
<% trail Recaptcha %>
<p>
<button type="submit" name="send" value="1"><% gettext Send %></button>
<a href="" class="cancel"><% gettext Cancel %></a>

View file

@ -198,8 +198,8 @@ Membership.prototype.contact_action = function() {
if (!req.postParams.text) {
throw Error(gettext('Please enter the message text.'));
}
Feature.invoke('recaptcha', function() {
return this.verify(req.postParams);
Trail.invoke('Recaptcha', function () {
this.verify(req.postParams);
});
this.notify(req.action, this.creator.email, session.user ?
gettext('[{0}] Message from user {1}', root.title, session.user.name) :

View file

@ -1,10 +1,34 @@
// The Antville Project
// http://code.google.com/p/antville
//
// Copyright 20012014 by the Workers of Antville.
//
// 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.
/**
* @fileOverview Defines the Trails mountpoint.
*/
Trails.load = function (trails) {
trails || (trails = []);
trails instanceof Array || (trails = [trails]);
if (trails instanceof Array === false) {
trails = String(trails).split(',');
}
trails.forEach(function (name) {
name = name.trim();
var repository = new helma.File(app.dir, '../trails/' + name);
if (repository.exists()) {
console.log('Adding trail', repository.toString());
//console.log('Adding trail', repository.toString());
app.addRepository(repository.toString());
app.data.trails.push(name);
}

View file

@ -91,7 +91,7 @@ else <% if <% site.mode %> is restricted then "" else
<div class='uk-form-row uk-margin-top'>
<fieldset>
<legend><% gettext Connections %></legend>
<% feature connect context=profile default=— %>
<% trail Connect context=profile default=— %>
</fieldset>
</div>
<div class='uk-form-row uk-margin-top'>

View file

@ -1,326 +0,0 @@
// The Antville Project
// http://code.google.com/p/antville
//
// Copyright 20012014 by the Workers of Antville.
//
// 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.
/**
* @fileoverview Defines the Antville Connect Feature.
*/
app.addRepository(app.dir + "/../extra/connect/scribe-1.3.0.jar");
// FIXME: Connecting with Twitter and Google currently does not return an e-mail address.
// Instead, noreplay@antville.org is used which is very poor and should be fixed ASAP.
// FIXME: Might be good to somehow define the action together with its permissions...
Members.prototype.connect_action = function() {
try {
var connect = Feature.get("connect");
switch (req.data.type) {
case "facebook":
connect.facebook(req);
break;
case "google":
connect.google(req);
break;
case "twitter":
connect.scribe(req.data.type);
break;
}
} catch (ex) {
session.logout();
res.message = String(ex);
res.redirect(res.handlers.members.href("login"));
}
JSON.sendPaddedResponse(this._parent.stories.getPermission("create"));
res.redirect(User.getLocation() || res.handlers.site.href());
return;
}
Members.prototype.disconnect_action = function() {
switch (req.data.type) {
case "facebook":
case "google":
case "twitter":
res.handlers.membership.creator.deleteMetadata(req.data.type + "_id");
break;
}
res.redirect(req.data.http_referer);
return;
}
Feature.add("connect", "http://code.google.com/p/antville/wiki/ConnectFeature", {
_getPermission: function(action) {
if (this.constructor === Members) {
switch (action) {
case "connect":
return true;
case "disconnect":
return User.require(User.REGULAR);
}
}
},
main: function(options) {
var defaultDomain = getProperty("domain.*");
var domain = getProperty("domain." + res.handlers.site.name);
if (defaultDomain && domain && !domain.endsWith(defaultDomain)) {
return;
}
var suffix = options.context ? "_" + options.context : "";
getProperty("connect.facebook.id") && renderSkin("connect#facebook" + suffix);
getProperty("connect.google.id") && renderSkin("connect#google" + suffix);
getProperty("connect.twitter.id") && renderSkin("connect#twitter" + suffix);
},
getUserByConnection: function(type, id) {
var user;
var connections = root.connections.get(id);
if (connections) {
connections.forEach(function(index) {
if (this.name === type + "_id") {
user = this.parent;
}
});
}
return user;
},
scribe: function(type) {
var name = type.titleize();
var appId = getProperty("connect." + type + ".id");
var secret = getProperty("connect." + type + ".key");
if (!secret || req.data.denied) {
throw Error(gettext("Connecting with {0} failed. {1} Please try again.", name,
gettext("You denied the request.")));
}
if (req.isPost()) {
try {
User.login(req.postParams);
} catch (ex) { }
}
var scribe = Packages.org.scribe;
var provider, requestUrl, scope, getValues;
var headers = {};
switch (type) {
case "google":
provider = scribe.builder.api.GoogleApi;
requestUrl = "http://www-opensocial.googleusercontent.com/api/people/@me/@self";
scope = "http://www-opensocial.googleusercontent.com/api/people/";
headers["GData-Version"] = "3.0";
getValues = function(data) {
data = data.entry;
return {
id: data.id,
name: data.displayName,
email: data.email,
url: data.url
}
}
break;
case "twitter":
provider = scribe.builder.api.TwitterApi.SSL;
requestUrl = "https://api.twitter.com/1.1/account/verify_credentials.json";
getValues = function(data) {
return {
id: data.id_str,
name: data.screen_name,
email: data.email,
url: data.profileUrl
}
}
break;
}
var url = res.handlers.members.href(req.action) + "?type=" + type;
var service = new scribe.builder.ServiceBuilder()
.provider(provider)
.apiKey(appId)
.apiSecret(secret)
.callback(url);
if (scope) {
service.scope(scope);
}
var oauth = service.build();
var verifier = req.data.oauth_verifier;
if (!verifier) {
// Because the service provider will redirect back to this URL the
// request token needs to be stored in the session object
session.data.requestToken = oauth.getRequestToken();
res.redirect(oauth.getAuthorizationUrl(session.data.requestToken));
}
try {
var accessToken = oauth.getAccessToken(session.data.requestToken,
new scribe.model.Verifier(verifier));
} catch (ex) {
throw Error(gettext("Connecting with {0} failed. {1} Please try again.", name,
gettext("Something went wrong.")));
}
var request = new scribe.model.OAuthRequest(scribe.model.Verb.GET, requestUrl);
oauth.signRequest(accessToken, request);
for (let name in headers) {
request.addHeader(name, headers[name]);
}
var response = request.send();
var data = getValues(JSON.parse(response.getBody()));
var user = this.getUserByConnection(type, data.id);
if (!user) {
if (!session.user) {
var name = root.users.getAccessName(data.name);
user = User.register({
name: name,
hash: session.data.requestToken.getToken(),
email: data.email || root.replyTo,
url: data.url
});
session.login(user);
} else {
user = session.user;
}
user.setMetadata(type + "_id", data.id);
} else if (user !== session.user) {
user.touch();
session.login(user);
}
return;
},
facebook: function(req) {
var appId = getProperty("connect.facebook.id");
var secret = getProperty("connect.facebook.key");
if (!secret || req.data.error) {
throw Error(gettext("Could not connect with Facebook. ({0})", -1));
}
if (req.isPost()) {
try {
User.login(req.postParams);
} catch (ex) { }
}
var url = res.handlers.members.href(req.action) + "?type=facebook";
var code = req.data.code;
if (!code) {
res.redirect("https://www.facebook.com/dialog/oauth?client_id=" + appId +
"&scope=email&redirect_uri=" + url);
return;
}
var mime = getURL("https://graph.facebook.com/oauth/access_token?client_id=" + appId +
"&redirect_uri=" + url + "&client_secret=" + secret + "&code=" + code);
if (!mime || !mime.text) {
throw Error(gettext("Could not connect with Facebook. ({0})", -3));
}
var token = mime.text;
mime = getURL("https://graph.facebook.com/me?" + token);
if (!mime) {
throw Error(gettext("Could not connect with Facebook. ({0})", -4));
}
var content = Packages.org.apache.commons.io.IOUtils.toString(mime.inputStream);
if (!content) {
throw Error(gettext("Could not connect with Facebook. ({0})", -5));
}
var data = JSON.parse(content);
var user = this.getUserByConnection("facebook", data.id);
if (!user) {
if (!session.user) {
var name = root.users.getAccessName(data.name);
user = User.register({
name: name,
hash: token,
email: data.email,
url: data.link,
});
session.login(user);
} else {
user = session.user;
}
user.setMetadata("facebook_id", data.id);
} else if (user !== session.user) {
user.touch();
session.login(user);
}
return;
},
google: function(req) {
if (req.isPost()) {
try {
User.login(req.postParams);
} catch (ex) { }
}
var url = root.members.href('connect') + "?type=google";
if (req.data.code) {
var http = new helma.Http();
http.setMethod("POST");
http.setContent("code=" + encodeURIComponent(req.data.code) +
"&client_id=" + encodeURIComponent(getProperty("connect.google.id")) +
"&client_secret=" + encodeURIComponent(getProperty("connect.google.key")) +
"&redirect_uri=" + encodeURIComponent(url) + "&grant_type=authorization_code");
var response = http.getUrl("https://accounts.google.com/o/oauth2/token");
var data = JSON.parse(response.content);
var token = data.access_token;
var mime = getURL("https://www.googleapis.com/oauth2/v1/userinfo?access_token=" +
encodeURIComponent(data.access_token));
var data = JSON.parse(Packages.org.apache.commons.io.IOUtils.toString(mime.inputStream));
var user = this.getUserByConnection("google", data.id);
if (!user) {
if (!session.user) {
var name = root.users.getAccessName(data.name);
user = User.register({
name: name,
hash: token,
email: data.email,
url: data.link
});
session.login(user);
} else {
user = session.user;
}
user.setMetadata("google_id", data.id);
} else if (user !== session.user) {
user.touch();
session.login(user);
}
} else {
res.redirect("https://accounts.google.com/o/oauth2/auth?" +
"client_id=" + encodeURIComponent(getProperty("connect.google.id")) +
"&redirect_uri=" + encodeURIComponent(url) +
"&scope=" + encodeURIComponent("https://www.googleapis.com/auth/userinfo.profile") +
"+" + encodeURIComponent("https://www.googleapis.com/auth/userinfo.email") +
"&response_type=code");
}
}
});

View file

@ -1,77 +0,0 @@
<% #facebook %>
<script type="text/javascript">
document.writeln('<div class="antville-feature connect">');
document.writeln('<img src="<% root.static facebook_round.png %>" width=20 height=20/>');
document.writeln('<a href="" id="facebook_connect"><% gettext "Login with {0}" Facebook %></a>');
document.writeln('</div>');
$(function() {
$("#facebook_connect").click(function() {
$("form#login").attr("action", "<% site.members.href connect %>?type=facebook");
$("button#submit").click();
return false;
});
})
</script>
<% #twitter %>
<script type="text/javascript">
document.writeln('<div class="antville-feature connect">');
document.writeln('<img src="<% root.static twitter_round.png %>" width=20 height=20/>');
document.writeln('<a href="" id="twitter_connect"><% gettext "Login with {0}" Twitter %></a>');
document.writeln('</div>');
$(function() {
$("#twitter_connect").click(function() {
$("form#login").attr("action", "<% site.members.href connect %>?type=twitter");
$("button#submit").click();
return false;
});
})
</script>
<% #google %>
<script type="text/javascript">
document.writeln('<div class="antville-feature connect">');
document.writeln('<img src="<% root.static google_round.png %>" width=20 height=20/>');
document.writeln('<a href="" id="google_connect"><% gettext "Login with {0}" Google %></a>');
document.writeln('</div>');
$(function() {
$("#google_connect").click(function() {
$("form#login").attr("action", "<% site.members.href connect %>?type=google");
$("button#submit").click();
return false;
});
})
</script>
<% #facebook_profile %>
<div class="antville-feature connect">
<img src="<% root.static facebook_round.png %>" width=20 height=20/>
<% if <% membership.user.metadata facebook_id %> is null then
<% members.link "connect?type=facebook" <% gettext "Connect with {0}" Facebook %> %>
else
<% members.link <% membership.user.id prefix="disconnect?type=facebook&id=" %>
<% gettext "Disconnect from {0}" Facebook %> suffix=<br> %>
%>
</div>
<% #twitter_profile %>
<div class="antville-feature connect">
<img src="<% root.static twitter_round.png %>" width=20 height=20/>
<% if <% membership.user.metadata twitter_id %> is null then
<% members.link "connect?type=twitter" <% gettext "Connect with {0}" Twitter %> %>
else
<% members.link <% membership.user.id prefix="disconnect?type=twitter&id=" %>
<% gettext "Disconnect from {0}" Twitter %> suffix=<br> %>
%>
</div>
<% #google_profile %>
<div class="antville-feature connect">
<img src="<% root.static google_round.png %>" width=20 height=20/>
<% if <% membership.user.metadata google_id %> is null then
<% members.link "connect?type=google" <% gettext "Connect with {0}" Google %> %>
else
<% members.link <% membership.user.id prefix="disconnect?type=google&id=" %>
<% gettext "Disconnect from {0}" Google %> suffix=<br> %>
%>
</div>

View file

@ -1,20 +0,0 @@
# The Antville Project
# http://code.google.com/p/antville
#
# Copyright 20012014 by the Workers of Antville.
#
# 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.
connections = collection(Metadata)
connections.filter = parent_type = 'User' and name in ('facebook_id', 'twitter_id', 'google_id')
connections.group = value

Binary file not shown.

View file

@ -1,52 +0,0 @@
// The Antville Project
// http://code.google.com/p/antville
//
// Copyright 20012014 by the Workers of Antville.
//
// 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.
/**
* @fileoverview Defines the Antville proxy Feature.
*/
Root.prototype.proxy_action = function() {
var url = req.data.url;
if (!url) {
return;
}
var http = new helma.Http;
var data = http.getUrl(url);
if (!data.content) {
throw Error("Failed to retrieve URL.");
}
var callback = req.data.callback;
if (callback) {
res.contentType = "text/javascript";
res.write(JSON.pad(data.content, callback));
} else {
res.write(data.content);
}
return;
}
Feature.add("proxy", "http://code.google.com/p/antville/wiki/ProxyFeature", {
_getPermission: function(action) {
if (this._prototype in {Root: 1} &&
action === "proxy" && User.require(User.TRUSTED)) {
return true;
}
}
});

View file

@ -1,55 +0,0 @@
// The Antville Project
// http://code.google.com/p/antville
//
// Copyright 20012014 by the Workers of Antville.
//
// 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.
/**
* @fileoverview Defines the Antville reCAPTCHA Feature.
* @see http://www.google.com/recaptcha
*/
Feature.add("recaptcha", "http://code.google.com/p/antville/wiki/RecaptchaFeature", new function() {
var key = getProperty("recaptcha.key");
return {
main: function() {
if (key && !session.user) {
renderSkin("recaptcha", {id: getProperty("recaptcha.id")});
}
return;
},
verify: function(data) {
if (session.user) {
return;
}
var http = new helma.Http;
http.setTimeout(200);
http.setReadTimeout(300);
http.setMethod("POST");
http.setContent({
privatekey: key,
remoteip: req.data.http_remotehost,
challenge: data.recaptcha_challenge_field,
response: data.recaptcha_response_field
});
var request = http.getUrl("http://www.google.com/recaptcha/api/verify");
if (request.code === 200 && !request.content.startsWith("true")) {
throw Error(gettext("Please enter the correct words in the CAPTCHA box."));
}
return;
}
}
});

View file

@ -1,14 +0,0 @@
<script type="text/javascript">
var RecaptchaOptions = {
theme: 'default',
lang: '<% site.locale %>'
};
</script>
<script type="text/javascript" src="http://www.google.com/recaptcha/api/challenge?k=<% param.id %>">
</script>
<noscript>
<iframe src="http://www.google.com/recaptcha/api/noscript?k=<% param.id %>"
height="300" width="500" frameborder="0"></iframe><br>
<textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
<input type="hidden" name="recaptcha_response_field" value="manual_challenge">
</noscript>