mirror of
https://github.com/sismics/docs.git
synced 2024-11-25 23:27:57 +01:00
#161: password recovery by email (wip)
This commit is contained in:
parent
590bf74e98
commit
65937d6f4c
@ -18,5 +18,14 @@ public enum ConfigType {
|
|||||||
/**
|
/**
|
||||||
* Guest login.
|
* Guest login.
|
||||||
*/
|
*/
|
||||||
GUEST_LOGIN
|
GUEST_LOGIN,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMTP server configuration.
|
||||||
|
*/
|
||||||
|
SMTP_HOSTNAME,
|
||||||
|
SMTP_PORT,
|
||||||
|
SMTP_FROM,
|
||||||
|
SMTP_USERNAME,
|
||||||
|
SMTP_PASSWORD
|
||||||
}
|
}
|
||||||
|
@ -18,20 +18,15 @@ import javax.ws.rs.*;
|
|||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
import com.sismics.docs.core.constant.ConfigType;
|
import com.sismics.docs.core.constant.ConfigType;
|
||||||
import com.sismics.docs.core.constant.PermType;
|
|
||||||
import com.sismics.docs.core.dao.jpa.*;
|
import com.sismics.docs.core.dao.jpa.*;
|
||||||
import com.sismics.docs.core.dao.jpa.criteria.TagCriteria;
|
|
||||||
import com.sismics.docs.core.dao.jpa.dto.AclDto;
|
|
||||||
import com.sismics.docs.core.dao.jpa.dto.TagDto;
|
|
||||||
import com.sismics.docs.core.event.RebuildIndexAsyncEvent;
|
import com.sismics.docs.core.event.RebuildIndexAsyncEvent;
|
||||||
import com.sismics.docs.core.model.jpa.Acl;
|
import com.sismics.rest.util.ValidationUtil;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.log4j.Appender;
|
import org.apache.log4j.Appender;
|
||||||
import org.apache.log4j.Level;
|
import org.apache.log4j.Level;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.sismics.docs.core.model.context.AppContext;
|
|
||||||
import com.sismics.docs.core.model.jpa.File;
|
import com.sismics.docs.core.model.jpa.File;
|
||||||
import com.sismics.docs.core.model.jpa.User;
|
import com.sismics.docs.core.model.jpa.User;
|
||||||
import com.sismics.docs.core.util.ConfigUtil;
|
import com.sismics.docs.core.util.ConfigUtil;
|
||||||
@ -119,6 +114,57 @@ public class AppResource extends BaseResource {
|
|||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the SMTP server.
|
||||||
|
*
|
||||||
|
* @api {post} /app/config_smtp Configure the SMTP server
|
||||||
|
* @apiName PostAppConfigSmtp
|
||||||
|
* @apiGroup App
|
||||||
|
* @apiParam {String} hostname SMTP hostname
|
||||||
|
* @apiParam {Integer} port SMTP port
|
||||||
|
* @apiParam {String} from From address
|
||||||
|
* @apiParam {String} username SMTP username
|
||||||
|
* @apiParam {String} password SMTP password
|
||||||
|
* @apiError (client) ForbiddenError Access denied
|
||||||
|
* @apiPermission admin
|
||||||
|
* @apiVersion 1.5.0
|
||||||
|
*
|
||||||
|
* @param hostname SMTP hostname
|
||||||
|
* @param portStr SMTP port
|
||||||
|
* @param from From address
|
||||||
|
* @param username SMTP username
|
||||||
|
* @param password SMTP password
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("config_smtp")
|
||||||
|
public Response configSmtp(@FormParam("hostname") String hostname,
|
||||||
|
@FormParam("port") String portStr,
|
||||||
|
@FormParam("from") String from,
|
||||||
|
@FormParam("username") String username,
|
||||||
|
@FormParam("password") String password) {
|
||||||
|
if (!authenticate()) {
|
||||||
|
throw new ForbiddenClientException();
|
||||||
|
}
|
||||||
|
checkBaseFunction(BaseFunction.ADMIN);
|
||||||
|
ValidationUtil.validateRequired(hostname, "hostname");
|
||||||
|
ValidationUtil.validateInteger(portStr, "port");
|
||||||
|
ValidationUtil.validateRequired(from, "from");
|
||||||
|
|
||||||
|
ConfigDao configDao = new ConfigDao();
|
||||||
|
configDao.update(ConfigType.SMTP_HOSTNAME, hostname);
|
||||||
|
configDao.update(ConfigType.SMTP_PORT, portStr);
|
||||||
|
configDao.update(ConfigType.SMTP_FROM, from);
|
||||||
|
if (username != null) {
|
||||||
|
configDao.update(ConfigType.SMTP_USERNAME, username);
|
||||||
|
}
|
||||||
|
if (password != null) {
|
||||||
|
configDao.update(ConfigType.SMTP_PASSWORD, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the application logs.
|
* Retrieve the application logs.
|
||||||
*
|
*
|
||||||
@ -400,62 +446,4 @@ public class AppResource extends BaseResource {
|
|||||||
.add("status", "ok");
|
.add("status", "ok");
|
||||||
return Response.ok().entity(response.build()).build();
|
return Response.ok().entity(response.build()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add base ACLs to tags.
|
|
||||||
*
|
|
||||||
* @api {post} /app/batch/tag_acls Add base ACL to tags
|
|
||||||
* @apiDescription This resource must be used after migrating to 1.5.
|
|
||||||
* It will not do anything if base ACL are already present on tags.
|
|
||||||
* @apiName PostAppBatchTagAcls
|
|
||||||
* @apiGroup App
|
|
||||||
* @apiSuccess {String} status Status OK
|
|
||||||
* @apiError (client) ForbiddenError Access denied
|
|
||||||
* @apiPermission admin
|
|
||||||
* @apiVersion 1.5.0
|
|
||||||
*
|
|
||||||
* @return Response
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@Path("batch/tag_acls")
|
|
||||||
public Response batchTagAcls() {
|
|
||||||
if (!authenticate()) {
|
|
||||||
throw new ForbiddenClientException();
|
|
||||||
}
|
|
||||||
checkBaseFunction(BaseFunction.ADMIN);
|
|
||||||
|
|
||||||
// Get all tags
|
|
||||||
TagDao tagDao = new TagDao();
|
|
||||||
UserDao userDao = new UserDao();
|
|
||||||
List<TagDto> tagDtoList = tagDao.findByCriteria(new TagCriteria(), null);
|
|
||||||
|
|
||||||
// Add READ and WRITE ACLs
|
|
||||||
for (TagDto tagDto : tagDtoList) {
|
|
||||||
// Remove old ACLs
|
|
||||||
AclDao aclDao = new AclDao();
|
|
||||||
List<AclDto> aclDtoList = aclDao.getBySourceId(tagDto.getId());
|
|
||||||
String userId = userDao.getActiveByUsername(tagDto.getCreator()).getId();
|
|
||||||
|
|
||||||
if (aclDtoList.size() == 0) {
|
|
||||||
// Create read ACL
|
|
||||||
Acl acl = new Acl();
|
|
||||||
acl.setPerm(PermType.READ);
|
|
||||||
acl.setSourceId(tagDto.getId());
|
|
||||||
acl.setTargetId(userId);
|
|
||||||
aclDao.create(acl, userId);
|
|
||||||
|
|
||||||
// Create write ACL
|
|
||||||
acl = new Acl();
|
|
||||||
acl.setPerm(PermType.WRITE);
|
|
||||||
acl.setSourceId(tagDto.getId());
|
|
||||||
acl.setTargetId(userId);
|
|
||||||
aclDao.create(acl, userId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always return OK
|
|
||||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
|
||||||
.add("status", "ok");
|
|
||||||
return Response.ok().entity(response.build()).build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
/**
|
/**
|
||||||
* Login controller.
|
* Login controller.
|
||||||
*/
|
*/
|
||||||
angular.module('docs').controller('Login', function(Restangular, $scope, $rootScope, $state, $dialog, User, $translate) {
|
angular.module('docs').controller('Login', function(Restangular, $scope, $rootScope, $state, $dialog, User, $translate, $uibModal) {
|
||||||
$scope.codeRequired = false;
|
$scope.codeRequired = false;
|
||||||
|
|
||||||
// Get the app configuration
|
// Get the app configuration
|
||||||
@ -28,7 +28,7 @@ angular.module('docs').controller('Login', function(Restangular, $scope, $rootSc
|
|||||||
});
|
});
|
||||||
$state.go('document.default');
|
$state.go('document.default');
|
||||||
}, function(data) {
|
}, function(data) {
|
||||||
if (data.data.type == 'ValidationCodeRequired') {
|
if (data.data.type === 'ValidationCodeRequired') {
|
||||||
// A TOTP validation code is required to login
|
// A TOTP validation code is required to login
|
||||||
$scope.codeRequired = true;
|
$scope.codeRequired = true;
|
||||||
} else {
|
} else {
|
||||||
@ -40,4 +40,31 @@ angular.module('docs').controller('Login', function(Restangular, $scope, $rootSc
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Password lost
|
||||||
|
$scope.openPasswordLost = function () {
|
||||||
|
$uibModal.open({
|
||||||
|
templateUrl: 'partial/docs/passwordlost.html',
|
||||||
|
controller: 'LoginModalPasswordLost'
|
||||||
|
}).result.then(function (email) {
|
||||||
|
if (name === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a password lost email
|
||||||
|
Restangular.one('user').post('passwordLost', {
|
||||||
|
email: email
|
||||||
|
}).then(function () {
|
||||||
|
var title = $translate.instant('login.password_lost_sent_title');
|
||||||
|
var msg = $translate.instant('login.password_lost_sent_message', { email: email });
|
||||||
|
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
|
||||||
|
$dialog.messageBox(title, msg, btns);
|
||||||
|
}, function () {
|
||||||
|
var title = $translate.instant('login.password_lost_error_title');
|
||||||
|
var msg = $translate.instant('login.password_lost_error_message', { email: email });
|
||||||
|
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
|
||||||
|
$dialog.messageBox(title, msg, btns);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
});
|
});
|
@ -0,0 +1,11 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login modal password lost controller.
|
||||||
|
*/
|
||||||
|
angular.module('docs').controller('LoginModalPasswordLost', function ($scope, $uibModalInstance) {
|
||||||
|
$scope.email = '';
|
||||||
|
$scope.close = function(name) {
|
||||||
|
$uibModalInstance.close(name);
|
||||||
|
}
|
||||||
|
});
|
@ -48,6 +48,7 @@
|
|||||||
<script src="app/docs/app.js" type="text/javascript"></script>
|
<script src="app/docs/app.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/Main.js" type="text/javascript"></script>
|
<script src="app/docs/controller/Main.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/Login.js" type="text/javascript"></script>
|
<script src="app/docs/controller/Login.js" type="text/javascript"></script>
|
||||||
|
<script src="app/docs/controller/LoginModalPasswordLost.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/Navigation.js" type="text/javascript"></script>
|
<script src="app/docs/controller/Navigation.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/Footer.js" type="text/javascript"></script>
|
<script src="app/docs/controller/Footer.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/document/Document.js" type="text/javascript"></script>
|
<script src="app/docs/controller/document/Document.js" type="text/javascript"></script>
|
||||||
|
@ -9,7 +9,17 @@
|
|||||||
"submit": "Sign in",
|
"submit": "Sign in",
|
||||||
"login_as_guest": "Login as guest",
|
"login_as_guest": "Login as guest",
|
||||||
"login_failed_title": "Login failed",
|
"login_failed_title": "Login failed",
|
||||||
"login_failed_message": "Username or password invalid"
|
"login_failed_message": "Username or password invalid",
|
||||||
|
"password_lost_btn": "Password lost?",
|
||||||
|
"password_lost_sent_title": "Password reset email sent",
|
||||||
|
"password_lost_sent_message": "An email has been sent to <strong>{{ email }}</strong> to reset your password",
|
||||||
|
"password_lost_error_title": "Password reset error",
|
||||||
|
"password_lost_error_message": "Unable to send a password reset email, please contact your administrator for a manual reset"
|
||||||
|
},
|
||||||
|
"passwordlost": {
|
||||||
|
"title": "Password lost",
|
||||||
|
"message": "Please enter your email address to receive a password reset link",
|
||||||
|
"submit": "Reset my password"
|
||||||
},
|
},
|
||||||
"index": {
|
"index": {
|
||||||
"toggle_navigation": "Toggle navigation",
|
"toggle_navigation": "Toggle navigation",
|
||||||
|
@ -44,7 +44,11 @@
|
|||||||
<span class="glyphicon glyphicon-ok"></span> {{ 'login.submit' | translate }}
|
<span class="glyphicon glyphicon-ok"></span> {{ 'login.submit' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p class="text-center lead" ng-if="app.guest_login"> </p>
|
<div class="text-center well-sm btn-password-lost">
|
||||||
|
<a href ng-click="openPasswordLost()">{{ 'login.password_lost_btn' | translate }}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-center" ng-if="app.guest_login"> </p>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-default btn-block" ng-if="app.guest_login" ng-click="loginAsGuest()">
|
<button type="submit" class="btn btn-default btn-block" ng-if="app.guest_login" ng-click="loginAsGuest()">
|
||||||
<span class="glyphicon glyphicon-user"></span> {{ 'login.login_as_guest' | translate }}
|
<span class="glyphicon glyphicon-user"></span> {{ 'login.login_as_guest' | translate }}
|
||||||
|
17
docs-web/src/main/webapp/src/partial/docs/passwordlost.html
Normal file
17
docs-web/src/main/webapp/src/partial/docs/passwordlost.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<form name="form">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>{{ 'passwordlost.title' | translate }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
<label for="share-result">{{ 'passwordlost.message' | translate }}</label>
|
||||||
|
<input name="email" class="form-control" type="email" required id="share-result" ng-model="email" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button ng-click="close(email)" class="btn btn-primary" ng-disabled="!form.$valid">
|
||||||
|
<span class="glyphicon glyphicon-envelope"></span> {{ 'passwordlost.submit' | translate }}
|
||||||
|
</button>
|
||||||
|
<button ng-click="close(null)" class="btn btn-default">{{ 'cancel' | translate }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
@ -343,6 +343,10 @@ input[readonly].share-link {
|
|||||||
.help-block, .checkbox {
|
.help-block, .checkbox {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-password-lost {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styling for the ngProgress itself */
|
/* Styling for the ngProgress itself */
|
||||||
|
@ -189,4 +189,23 @@ public class TestAppResource extends BaseJerseyTest {
|
|||||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, guestToken)
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, guestToken)
|
||||||
.get(JsonObject.class);
|
.get(JsonObject.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test SMTP configuration changes.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSmtpConfiguration() {
|
||||||
|
// Login admin
|
||||||
|
String adminToken = clientUtil.login("admin", "admin", false);
|
||||||
|
|
||||||
|
// Change SMTP configuration
|
||||||
|
target().path("/app/config_smtp").request()
|
||||||
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||||
|
.post(Entity.form(new Form()
|
||||||
|
.param("hostname", "smtp.sismics.com")
|
||||||
|
.param("port", "1234")
|
||||||
|
.param("from", "contact@sismics.com")
|
||||||
|
.param("username", "sismics")
|
||||||
|
), JsonObject.class);
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user