Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Email verifications for signups and lots of refinement #556

Merged
merged 3 commits into from
Jun 9, 2016
Merged
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
73 changes: 62 additions & 11 deletions src/org/loklak/api/cms/SignUpServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

import org.eclipse.jetty.util.log.Log;
import org.json.JSONObject;
import org.loklak.data.DAO;
import org.loklak.server.APIException;
import org.loklak.server.APIHandler;
import org.loklak.server.APIServiceLevel;
import org.loklak.server.AbstractAPIHandler;
import org.loklak.server.Authentication;
import org.loklak.server.Authorization;
import org.loklak.server.ClientCredential;
import org.loklak.server.ClientIdentity;
Expand All @@ -44,7 +46,12 @@ public APIServiceLevel getDefaultServiceLevel() {

@Override
public APIServiceLevel getCustomServiceLevel(Authorization rights) {
return APIServiceLevel.ADMIN;
if(rights.isAdmin()){
return APIServiceLevel.ADMIN;
} else if(rights.getIdentity() != null){
return APIServiceLevel.LIMITED;
}
return APIServiceLevel.PUBLIC;
}

public String getAPIPath() {
Expand All @@ -54,14 +61,40 @@ public String getAPIPath() {
@Override
public JSONObject serviceImpl(Query post, Authorization rights) throws APIException {

APIServiceLevel serviceLevel = getCustomServiceLevel(rights);

JSONObject result = new JSONObject();

if(!rights.isAdmin() && !"true".equals(DAO.getConfig("users.public.signup", "false"))){
result.put("status", "error");
result.put("reason", "Public signup disabled");
// check for verification
if(post.get("validateEmail", false) && serviceLevel == APIServiceLevel.LIMITED){
ClientCredential credential = new ClientCredential(ClientCredential.Type.passwd_login, rights.getIdentity().getName());
Authentication authentication = new Authentication(credential, DAO.authentication);
if(authentication.has("activated")){
authentication.put("activated", true);
}
result.put("status", "ok");
result.put("reason", "ok");
return result;
}

boolean activated = true;
boolean sendEmail = false;

if(serviceLevel != APIServiceLevel.ADMIN){
switch(DAO.getConfig("users.public.signup", "false")){
case "false":
result.put("status", "error");
result.put("reason", "Public signup disabled");
return result;
case "admin":
activated = false;
break;
case "email":
activated = false;
sendEmail = true;
}
}

if(post.get("signup",null) == null || post.get("password", null) == null){
result.put("status", "error");
result.put("reason", "signup or password empty");
Expand All @@ -80,20 +113,38 @@ public JSONObject serviceImpl(Query post, Authorization rights) throws APIExcept


ClientCredential credential = new ClientCredential(ClientCredential.Type.passwd_login, signup);
if (DAO.authentication.has(credential.toString())) {
Authentication authentication = new Authentication(credential, DAO.authentication);

if (authentication.getIdentity() != null) {
result.put("status", "error");
result.put("reason", "email already taken");
return result;
}

JSONObject user_obj = new JSONObject();
String salt = createRandomString(20);
user_obj.put("salt", salt);
user_obj.put("passwordHash", getHash(password, salt));
ClientIdentity identity = new ClientIdentity(ClientIdentity.Type.email, credential.getName());
user_obj.put("id",identity.toString());
DAO.authentication.put(credential.toString(), user_obj, credential.isPersistent());
authentication.setIdentity(identity);

String salt = createRandomString(20);
authentication.put("salt", salt);
authentication.put("passwordHash", getHash(password, salt));
authentication.put("activated", activated);

if(sendEmail){
String token = createRandomString(30);
ClientCredential loginToken = new ClientCredential(ClientCredential.Type.login_token, token);
Authentication tokenAuthentication = new Authentication(loginToken, DAO.authentication);
tokenAuthentication.setIdentity(identity);
tokenAuthentication.setExpireTime(7 * 24 * 60 * 60);
tokenAuthentication.put("one_time", true);

// TODO: send email with token
Log.getLog().info("verfication link: http://localhost:9000/api/signup.json?login_token="+token+"&validateEmail=true&request_session=true");
}

//Log.getLog().info(DAO.authentication.getVolatile().toString());
//Log.getLog().info(DAO.authentication.getPersistent().toString());


result.put("status", "ok");
result.put("reason", "ok");
return result;
Expand Down
84 changes: 41 additions & 43 deletions src/org/loklak/server/AbstractAPIHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,7 @@ private void process(HttpServletRequest request, HttpServletResponse response, Q
ClientIdentity identity = getIdentity(request, response);

// user authorization: we use the identification of the user to get the assigned authorization
JSONObject authorization_obj = null;
if (DAO.authorization.has(identity.toString())) {
authorization_obj = DAO.authorization.getJSONObject(identity.toString());
} else {
authorization_obj = new JSONObject();
DAO.authorization.put(identity.toString(), authorization_obj, identity.isPersistent());
}
Authorization authorization = new Authorization(authorization_obj, DAO.authorization, identity);
Authorization authorization = new Authorization(identity, DAO.authorization);

// user accounting: we maintain static and persistent user data; we again search the accounts using the usder identity string
//JSONObject accounting_persistent_obj = DAO.accounting_persistent.has(user_id) ? DAO.accounting_persistent.getJSONObject(anon_id) : DAO.accounting_persistent.put(user_id, new JSONObject()).getJSONObject(user_id);
Expand Down Expand Up @@ -205,30 +198,32 @@ else if(getLoginCookie(request) != null){
Cookie loginCookie = getLoginCookie(request);

ClientCredential credential = new ClientCredential(ClientCredential.Type.cookie, loginCookie.getValue());
Authentication authentication = new Authentication(credential, DAO.authentication);

if(DAO.authentication.has(credential.toString())){

Authentication authentication = new Authentication(DAO.authentication.getJSONObject(credential.toString()), DAO.authentication);
ClientIdentity identity = authentication.getIdentity();
if(authentication.getIdentity() != null){

if(authentication.checkExpireTime() && identity != null){
if(authentication.checkExpireTime()){

//reset cookie validity time
authentication.setExpireTime(defaultCookieTime);
loginCookie.setMaxAge(defaultCookieTime.intValue());
loginCookie.setPath("/"); // bug. The path gets reset
response.addCookie(loginCookie);

return identity;
return authentication.getIdentity();
}
else{
authentication.delete();

// delete cookie if set
deleteLoginCookie(response);

Log.getLog().info("Invalid login try via cookie from host: " + request.getRemoteHost());
}
}
else{
authentication.delete();

// delete cookie if set
deleteLoginCookie(response);

Expand All @@ -249,25 +244,22 @@ else if (request.getParameter("login") != null && request.getParameter("password
} catch (UnsupportedEncodingException e) {}

ClientCredential credential = new ClientCredential(ClientCredential.Type.passwd_login, login);
Authentication authentication = new Authentication(credential, DAO.authentication);

// check if password is valid
if(DAO.authentication.has(credential.toString())){
if(authentication.getIdentity() != null){

JSONObject authentication_obj = DAO.authentication.getJSONObject(credential.toString());
if(authentication.has("activated") && !authentication.getBoolean("activated")){

if(authentication_obj.has("passwordHash") && authentication_obj.has("salt")){

String passwordHash = authentication_obj.getString("passwordHash");
String salt = authentication_obj.getString("salt");

Authentication authentication = new Authentication(authentication_obj, DAO.authentication);
ClientIdentity identity = authentication.getIdentity();

if(getHash(password, salt).equals(passwordHash)){

// identity can be null
if(identity != null){
if(authentication.has("passwordHash") && authentication.has("salt")){

String passwordHash = authentication.getString("passwordHash");
String salt = authentication.getString("salt");

ClientIdentity identity = authentication.getIdentity();

if(getHash(password, salt).equals(passwordHash)){

// only create a cookie or session if requested (by login page)
if("true".equals(request.getParameter("request_cookie"))){

Expand Down Expand Up @@ -295,29 +287,42 @@ else if("true".equals(request.getParameter("request_session"))){
Log.getLog().info("login for user: " + identity.getName() + " via passwd from host: " + request.getRemoteHost());

return identity;
}
}
Log.getLog().info("Invalid login try for user: " + identity.getName() + " via passwd from host: " + request.getRemoteHost());
}
Log.getLog().info("Invalid login try for user: " + identity.getName() + " via passwd from host: " + request.getRemoteHost());
Log.getLog().info("Invalid login try for user: " + credential.getName() + " from host: " + request.getRemoteHost() + " : password or salt missing in database");
}
Log.getLog().info("Invalid login try for user: " + credential.getName() + " from host: " + request.getRemoteHost() + " : user not activated yet");
}
else{
authentication.delete();
Log.getLog().info("Invalid login try for unknown user: " + credential.getName() + " via passwd from host: " + request.getRemoteHost());
}
}
else if (request.getParameter("login_token") != null){
ClientCredential credential = new ClientCredential(ClientCredential.Type.login_token, request.getParameter("login_token"));
Authentication authentication = new Authentication(credential, DAO.authentication);


// check if login_token is valid
if(DAO.authentication.has(credential.toString())){
Authentication authentication = new Authentication(DAO.authentication.getJSONObject(credential.toString()), DAO.authentication);
if(authentication.getIdentity() != null){
ClientIdentity identity = authentication.getIdentity();

if(authentication.checkExpireTime() && identity != null){
if(authentication.checkExpireTime()){
Log.getLog().info("login for user: " + identity.getName() + " via token from host: " + request.getRemoteHost());

if("true".equals(request.getParameter("request_session"))){
request.getSession().setAttribute("identity",identity);
}
if(authentication.has("one_time") && authentication.getBoolean("one_time")){
authentication.delete();
}
return identity;
}
Log.getLog().info("Invalid login try for user: " + identity.getName() + " via token from host: " + request.getRemoteHost());
}
Log.getLog().info("Invalid login token from host: " + request.getRemoteHost());
authentication.delete();
}

return getAnonymousIdentity(request);
Expand All @@ -329,18 +334,11 @@ else if (request.getParameter("login_token") != null){
*/
private ClientIdentity getAnonymousIdentity(HttpServletRequest request){
ClientCredential credential = new ClientCredential(ClientCredential.Type.host, request.getRemoteHost());
Authentication authentication = new Authentication(credential, DAO.authentication);

JSONObject authentication_obj = null;
if (DAO.authentication.has(credential.toString())) {
authentication_obj = DAO.authentication.getJSONObject(credential.toString());
}
else{
authentication_obj = new JSONObject();
}
authentication_obj.put("expires_on", Instant.now().getEpochSecond() + defaultAnonymousTime);
authentication.setExpireTime(Instant.now().getEpochSecond() + defaultAnonymousTime);

DAO.authentication.put(credential.toString(), authentication_obj, credential.isPersistent());
return new ClientIdentity(credential.toString());
return authentication.getIdentity();
}

/**
Expand Down
46 changes: 44 additions & 2 deletions src/org/loklak/server/Authentication.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class Authentication {

private JsonTray parent;
private JSONObject json;
private String parentKey;

/**
* create a new authentication object. The given json object must be taken
Expand All @@ -39,8 +40,18 @@ public class Authentication {
* @param json object for storage of the authorization
* @param parent the parent file or null if there is no parent file (no persistency)
*/
public Authentication(JSONObject json, JsonTray parent) {
this.json = json;
public Authentication(ClientCredential credential, JsonTray parent) {
parentKey = credential.toString();
if(parent != null){
if(parent.has(parentKey)){
this.json = parent.getJSONObject(parentKey);
}
else{
parent.put(parentKey, new JSONObject(), credential.isPersistent());
this.json = parent.getJSONObject(parentKey);
}
}
else this.json = new JSONObject();
this.parent = parent;
}

Expand All @@ -64,4 +75,35 @@ public boolean checkExpireTime(){
if(this.json.has("expires_on") && this.json.getLong("expires_on") > Instant.now().getEpochSecond()) return true;
return false;
}

public Object get(String key){
return this.json.get(key);
}

public String getString(String key){
return this.json.getString(key);
}

public boolean getBoolean(String key){
return this.json.getBoolean(key);
}

public boolean has(String key){
return this.json.has(key);
}

public void put(String key, Object value){
this.json.put(key, value);
if (this.parent != null && getIdentity().isPersistent()) this.parent.commit();
}

public void remove(String key){
this.json.remove(key);
if (this.parent != null && getIdentity().isPersistent()) this.parent.commit();
}

public void delete(){
this.parent.remove(parentKey);
parent = null;
}
}
14 changes: 10 additions & 4 deletions src/org/loklak/server/Authorization.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@

package org.loklak.server;

import org.json.JSONArray;
import org.json.JSONObject;
import org.loklak.tools.storage.JsonFile;
import org.loklak.tools.storage.JsonTray;

/**
Expand All @@ -45,8 +43,16 @@ public class Authorization {
* @param json object for storage of the authorization
* @param parent the parent file or null if there is no parent file (no persistency)
*/
public Authorization(final JSONObject json, JsonTray parent, ClientIdentity identity) {
this.json = json;
public Authorization(ClientIdentity identity, JsonTray parent) {
if(parent != null){
if (parent.has(identity.toString())) {
this.json = parent.getJSONObject(identity.toString());
} else {
parent.put(identity.toString(), new JSONObject(), identity.isPersistent());
this.json = parent.getJSONObject(identity.toString());
}
}
else this.json = new JSONObject();
this.parent = parent;
this.accounting = null;
this.identity = identity;
Expand Down
4 changes: 4 additions & 0 deletions src/org/loklak/tools/CacheMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,8 @@ public boolean exist(K key) {
return exist;
}

public LinkedHashMap<K,V> getMap(){
return map;
}

}
Loading