master moved from 43ab6c1b9f0e to 68df28d58697 7 new revisions: Revision: 101f6cb61687 Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sat Feb 8 09:19:06 2014 UTC Log: + better authorization system... http://code.google.com/p/haiku-depot-web-app/source/detail?r=101f6cb61687 Revision: d02f16acd3fc Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sat Feb 8 09:30:39 2014 UTC Log: + add gui for triggering repository imports http://code.google.com/p/haiku-depot-web-app/source/detail?r=d02f16acd3fc Revision: b3fbcf57101d Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sat Feb 8 10:16:22 2014 UTC Log: + small changes from checks http://code.google.com/p/haiku-depot-web-app/source/detail?r=b3fbcf57101d Revision: 11b560e3e392 Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sat Feb 8 11:31:22 2014 UTC Log: + better pagination action directives http://code.google.com/p/haiku-depot-web-app/source/detail?r=11b560e3e392 Revision: b001fd64677c Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sun Feb 9 06:49:48 2014 UTC Log: + create repository api... http://code.google.com/p/haiku-depot-web-app/source/detail?r=b001fd64677c Revision: 04264ff1b771 Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sun Feb 9 10:00:28 2014 UTC Log: + add and edit repository functionality http://code.google.com/p/haiku-depot-web-app/source/detail?r=04264ff1b771 Revision: 68df28d58697 Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sun Feb 9 10:38:52 2014 UTC Log: + documentation update... http://code.google.com/p/haiku-depot-web-app/source/detail?r=68df28d58697 ============================================================================== Revision: 101f6cb61687 Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sat Feb 8 09:19:06 2014 UTC Log: + better authorization system + list repositories has option to see disabled data + enable and disable actions on view repository http://code.google.com/p/haiku-depot-web-app/source/detail?r=101f6cb61687 Added:/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/AuthorizationTargetType.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/CheckAuthorizationRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/CheckAuthorizationResult.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/GetRepositoryRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/GetRepositoryResult.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/TriggerImportRepositoryRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/TriggerImportRepositoryResult.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/TargetType.java
/haikudepotserver-webapp/src/main/webapp/css/listrepositories.css/haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/activeindicator.html /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifpermissiondirective.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifpkgpermissiondirective.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifrepositorypermissiondirective.js
/haikudepotserver-webapp/src/test/resources/logback.xml Modified:/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesResult.java
/haikudepotserver-parent/pom.xml/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Pkg.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java
/haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css /haikudepotserver-webapp/src/main/webapp/css/home.css/haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js
/haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html/haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js
/haikudepotserver-webapp/src/main/webapp/js/app/controller/more.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html/haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/activeindicatordirective.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js
/haikudepotserver-webapp/src/main/webapp/js/app/routes.js/haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js
/haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js/haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/MiscelaneousApiIT.java /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/PkgApiIT.java /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/RepositoryApiIT.java
======================================= --- /dev/null+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/AuthorizationTargetType.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,16 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.api1.model; + +/**+ * <p>This enum is used to identify what type of target the authorization applies to.</p>
+ */ + +public enum AuthorizationTargetType { + PKG, + USER, + REPOSITORY +} ======================================= --- /dev/null+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/CheckAuthorizationRequest.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,49 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.api1.model.miscellaneous; + +import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType; + +import java.util.List; + +public class CheckAuthorizationRequest { + + public List<AuthorizationTargetAndPermission> targetAndPermissions; + + public static class AuthorizationTargetAndPermission { + + /**+ * <p>The target type defines what sort of object you want to check your authorization for. The #targetIdentifier
+ * then identifies an instance of that type.</p> + */ + + public AuthorizationTargetType targetType; + + /**+ * This identifier will identify an instance of the #targetType that has the authorization applied to it. Some + * permissions may not require a target identifier; in which case this value can be supplied as null.
+ */ + + public String targetIdentifier; + + /**+ * <p>This is a list of permissions that the client would like to check for in the context of the target
+ * identified by other parameters in this request.</p> + */ + + public String permissionCode; + + public AuthorizationTargetAndPermission() { + } ++ public AuthorizationTargetAndPermission(AuthorizationTargetType targetType, String targetIdentifier, String permissionCode) {
+ this.targetType = targetType; + this.targetIdentifier = targetIdentifier; + this.permissionCode = permissionCode; + } + } + +} ======================================= --- /dev/null+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/CheckAuthorizationResult.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,47 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.api1.model.miscellaneous; + +import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType; + +import java.util.List; + +public class CheckAuthorizationResult { + + public List<AuthorizationTargetAndPermission> targetAndPermissions; + + public static class AuthorizationTargetAndPermission { + + /**+ * <p>The target type defines what sort of object you want to check your authorization for. The #targetIdentifier
+ * then identifies an instance of that type.</p> + */ + + public AuthorizationTargetType targetType; + + /**+ * This identifier will identify an instance of the #targetType that has the authorization applied to it. Some + * permissions may not require a target identifier; in which case this value can be supplied as null.
+ */ + + public String targetIdentifier; + + /**+ * <p>This is a list of permissions that the client would like to check for in the context of the target
+ * identified by other parameters in this request.</p> + */ + + public String permissionCode; + + /**+ * <p>This boolean will be true if the target is authorized; false if not.</p>
+ */ + + public Boolean authorized; + } + + +} ======================================= --- /dev/null+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/GetRepositoryRequest.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,12 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.api1.model.repository; + +public class GetRepositoryRequest { + + public String code; + +} ======================================= --- /dev/null+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/GetRepositoryResult.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,17 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.api1.model.repository; + +public class GetRepositoryResult { + + public Boolean active; + public String code; + public String architectureCode; + public Long createTimestamp; + public Long modifyTimestamp; + public String url; + +} ======================================= --- /dev/null+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/TriggerImportRepositoryRequest.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,12 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.api1.model.repository; + +public class TriggerImportRepositoryRequest { + + public String code; + +} ======================================= --- /dev/null+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/TriggerImportRepositoryResult.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,10 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.api1.model.repository; + +public class TriggerImportRepositoryResult { + +} ======================================= --- /dev/null+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryRequest.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,22 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.api1.model.repository; + +import java.util.List; + +public class UpdateRepositoryRequest { + + public enum Filter { + ACTIVE + }; + + public String code; + + public Boolean active; + + public List<Filter> filter; + +} ======================================= --- /dev/null+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryResult.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,9 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.api1.model.repository; + +public class UpdateRepositoryResult { +} ======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,151 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.security; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import org.apache.cayenne.DataObject; +import org.apache.cayenne.ObjectContext; +import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType; +import org.haikuos.haikudepotserver.dataobjects.Pkg; +import org.haikuos.haikudepotserver.dataobjects.Repository; +import org.haikuos.haikudepotserver.dataobjects.User; +import org.haikuos.haikudepotserver.security.model.Permission; +import org.haikuos.haikudepotserver.security.model.TargetType; +import org.springframework.stereotype.Service; + +/**+ * <P>This class will provide functions around authorization. Some of the model for this is provided
+ * by the API objects.</p> + */ + +@Service +public class AuthorizationService { ++ private AuthorizationTargetType deriveTargetType(DataObject dataObject) {
+ if(null==dataObject) + return null; + + if(User.class.isAssignableFrom(dataObject.getClass())) { + return AuthorizationTargetType.USER; + } + + if(Pkg.class.isAssignableFrom(dataObject.getClass())) { + return AuthorizationTargetType.PKG; + } + + if(Repository.class.isAssignableFrom(dataObject.getClass())) { + return AuthorizationTargetType.REPOSITORY; + } ++ throw new IllegalStateException("the data object type '"+dataObject.getClass().getSimpleName()+"' is not handled");
+ } + + public boolean check( + ObjectContext objectContext, + User authenticatedUser, + TargetType targetType, + String targetIdentifier, + Permission permission) { + Preconditions.checkNotNull(permission); + Preconditions.checkNotNull(objectContext); + + DataObject target = null; + + if(null!=targetType) { + + Optional<? extends DataObject> targetOptional = null; + + if(Strings.isNullOrEmpty(targetIdentifier)) {+ throw new IllegalStateException("the target type is supplied, but no target identifier");
+ } + + switch(targetType) { + case PKG:+ targetOptional = Pkg.getByName(objectContext, targetIdentifier);
+ break; + + case REPOSITORY:+ targetOptional = Repository.getByCode(objectContext, targetIdentifier);
+ break; + + case USER:+ targetOptional = User.getByNickname(objectContext, targetIdentifier);
+ break; + + default:+ throw new IllegalStateException("the target type is not handled; "+targetType.name());
+ } ++ // if the object was not able to be found then we should bail-out and say that the permission
+ // does not apply. + + if(!targetOptional.isPresent()) { + return false; + } + + target = targetOptional.get(); + } + else { + if(!Strings.isNullOrEmpty(targetIdentifier)) {+ throw new IllegalStateException("the target type was supplied, but not the target identifier");
+ } + } + + return check(objectContext, authenticatedUser, target, permission); + } + + /**+ * <p>This method will return true if the permission applies in this situation.</p>
+ */ + + public boolean check( + ObjectContext objectContext, + User authenticatedUser, + DataObject target, + Permission permission) { + + Preconditions.checkNotNull(permission); + Preconditions.checkNotNull(objectContext);+ Preconditions.checkState(deriveTargetType(target) == permission.getRequiredTargetType());
+ + switch(permission) { + + case REPOSITORY_EDIT: + case REPOSITORY_IMPORT:+ return null!=authenticatedUser && authenticatedUser.getIsRoot();
+ + case REPOSITORY_VIEW: + Repository repository = (Repository) target;+ return repository.getActive() || (null!=authenticatedUser && authenticatedUser.getIsRoot());
+ + case REPOSITORY_LIST: + return true; + + case REPOSITORY_LIST_INACTIVE:+ return null!=authenticatedUser && authenticatedUser.getIsRoot();
+ + case USER_VIEW: + case USER_EDIT: + case USER_CHANGEPASSWORD: + return + null!=authenticatedUser+ && (authenticatedUser.getIsRoot() || authenticatedUser.equals(target));
+ + case USER_LIST:+ return null!=authenticatedUser && authenticatedUser.getIsRoot();
+ + case PKG_EDITICON:+ return null!=authenticatedUser && authenticatedUser.getIsRoot();
+ + default:+ throw new IllegalStateException("unhandled permission; "+permission.name());
+ } + + } + +} ======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,35 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.security.model; + +import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType; + +public enum Permission { + + REPOSITORY_VIEW(AuthorizationTargetType.REPOSITORY), + REPOSITORY_EDIT(AuthorizationTargetType.REPOSITORY), + REPOSITORY_IMPORT(AuthorizationTargetType.REPOSITORY), + REPOSITORY_LIST(null), + REPOSITORY_LIST_INACTIVE(null), + + USER_VIEW(AuthorizationTargetType.USER), + USER_EDIT(AuthorizationTargetType.USER), + USER_CHANGEPASSWORD(AuthorizationTargetType.USER), + USER_LIST(null), + + PKG_EDITICON(AuthorizationTargetType.PKG); + + private AuthorizationTargetType requiredTargetType; + + Permission(AuthorizationTargetType requiredTargetType) { + this.requiredTargetType = requiredTargetType; + } + + public AuthorizationTargetType getRequiredTargetType() { + return requiredTargetType; + } + +} ======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/TargetType.java Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,16 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.security.model; + +/**+ * <p>This enum defines the entities onto which the authorization might occur.</p>
+ */ + +public enum TargetType { + PKG, + USER, + REPOSITORY +} ======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/webapp/css/listrepositories.css Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,16 @@ +/*+This specific layout works off the premise that there is only one table on this page.
+*/ + +.list-repositories .table-general > tbody > tr > td:nth-child(1) { + width: 10%; + text-align: center; +} + +.list-repositories .table-general > tbody > tr > td:nth-child(2) { + width:60%; +} + +.list-repositories .table-general > tbody > tr > td:nth-child(3) { + width:30%; +} ======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,36 @@ +<breadcrumbs items="breadcrumbItems"></breadcrumbs> + +<div class="content-container"> + + <dl> + <dt>Code</dt> + <dd><code>{{repository.code}}</code> </dd> + <dt>Active</dt>+ <dd><active-indicator state="repository.active"></active-indicator></dd>
+ <dt>URL</dt> + <dd><a href="{{repository.code}}">{{repository.url}}</a></dd> + <dt>Architecture</dt> + <dd>{{repository.architectureCode}}</dd> + <dt>Created</dt> + <dd>{{repository.createTimestamp|timestamp}}</dd> + <dt>Modified</dt> + <dd>{{repository.modifyTimestamp|timestamp}}</dd> + </dl> + + <ul>+ <li ng-show="canDeactivate()" repository="repository" show-if-repository-permission="'REPOSITORY_EDIT'">
+ <a href="" ng-click="goDeactivate()">Deactivate</a> + </li>+ <li ng-show="canActivate()" repository="repository" show-if-repository-permission="'REPOSITORY_EDIT'">
+ <a href="" ng-click="goActivate()">Reactivate</a> + </li>+ <li ng-show="repository.active" repository="repository" show-if-repository-permission="'REPOSITORY_IMPORT'">
+ <a href="" ng-click="goTriggerImport()">Trigger import</a> + </li> + </ul> + +</div> + +<div class="footer"></div> +<spinner spin="shouldSpin()"></spinner> + ======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,104 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +angular.module('haikudepotserver').controller( + 'ViewRepositoryController', + [ + '$scope','$log','$location','$routeParams', + 'jsonRpc','constants','userState','errorHandling','breadcrumbs', + function( + $scope,$log,$location,$routeParams, + jsonRpc,constants,userState,errorHandling,breadcrumbs) { + + $scope.breadcrumbItems = undefined; + $scope.repository = undefined; + var amUpdatingActive = false; + + refetchRepository(); + + $scope.shouldSpin = function() { + return undefined == $scope.repository; + } + + function updateActive(flag) { + + amUpdatingActive = true; + + jsonRpc.call( + constants.ENDPOINT_API_V1_REPOSITORY, + "updateRepository", + [{ + code : $routeParams.code, + active : flag, + filter : [ 'ACTIVE' ] + }] + ).then( + function(result) { + amUpdatingActive = false; + $scope.repository.active = flag;+ $log.info('did set the active flag on '+$scope.repository.code+' to '+flag);
+ }, + function(err) { + errorHandling.handleJsonRpcError(err); + } + ); + } + + $scope.canActivate = function() {+ return $scope.repository && !$scope.repository.active && !amUpdatingActive;
+ } + + $scope.canDeactivate = function() {+ return $scope.repository && $scope.repository.active && !amUpdatingActive;
+ } + + $scope.goActivate = function() { + updateActive(true); + } + + $scope.goDeactivate = function() { + updateActive(false); + } + + /**+ * <p>This function will initiate an import of a repository. These run sequentially so it may not happen
+ * immediately; it may be queued to go later.</p> + */ + + $scope.goTriggerImport = function() { + + } + + function refreshBreadcrumbItems() { + $scope.breadcrumbItems = [ + breadcrumbs.createMore(), + breadcrumbs.createListRepositories(), + breadcrumbs.createViewRepository($scope.repository) + ]; + } + + function refetchRepository() { + + $scope.repository = undefined; + + jsonRpc.call( + constants.ENDPOINT_API_V1_REPOSITORY, + "getRepository", + [{ code: $routeParams.code }] + ).then( + function(result) { + $scope.repository = result;+ $log.info('found '+$scope.repository.code+' repository');
+ refreshBreadcrumbItems(); + }, + function(err) { + errorHandling.handleJsonRpcError(err); + } + ); + } + + } + ] +); ======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/activeindicator.html Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,1 @@+<svg height="12" width="12"><circle cx="6" cy="6" r="6" fill="gray" ng-class="classes"></circle></svg>
======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifpermissiondirective.js Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,68 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +/**+ * <p>This directive is able to display the transcluded (material inside the element) if the permission holds. Note that
+ * this variant does not have a specific target.</p> + */ + +angular.module('haikudepotserver').directive('showIfPermission',[ + 'userState', function(userState) { + return { + restrict: 'A', + link : function($scope,element,attributes) { ++ var permissionCodeExpression = attributes['showIfPermission']; + var permissionCode = $scope.$eval(permissionCodeExpression);
+ + // by default we will hide it. + + element.addClass('app-hide'); + check(); ++ $scope.$watch(permissionCodeExpression, function(newValue,oldValue) {
+ permissionCode = newValue; + check(); + }); + + function check() { + if(!permissionCode) { + element.addClass('app-hide'); + } + else { + var targetAndPermissions = []; + + if(angular.isArray(permissionCode)) { + _.each(permissionCode, function(item) { + targetAndPermissions.push({ + targetType: null, + targetIdentifier : null, + permissionCode : item + }); + }); + } + else { + targetAndPermissions.push({ + targetType: null, + targetIdentifier : null, + permissionCode : ''+permissionCode + }); + } ++ userState.areAuthorized(targetAndPermissions).then(function(flag) {
+ if(flag) { + element.removeClass('app-hide'); + } + else { + element.addClass('app-hide'); + } + }); + } + } + + } + } + } +]); ======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifpkgpermissiondirective.js Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,75 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +/**+ * <p>This directive is able to display the transcluded (material inside the element) if the permission holds against
+ * the nominated package.</p> + */ + +angular.module('haikudepotserver').directive('showIfPkgPermission',[ + 'userState', function(userState) { + return { + restrict: 'A', + link : function($scope,element,attributes) { + + var pkgExpression = attributes['pkg'];+ var permissionCodeExpression = attributes['showIfPkgPermission'];
+ var pkg = $scope.$eval(pkgExpression);+ var permissionCode = $scope.$eval(permissionCodeExpression);
+ + // by default we will hide it. + + element.addClass('app-hide'); + check(); + + $scope.$watch(pkgExpression, function(newValue,oldValue) { + pkg = newValue; + check(); + }); ++ $scope.$watch(permissionCodeExpression, function(newValue,oldValue) {
+ permissionCode = newValue; + check(); + }); + + function check() { + if(!permissionCode || !pkg) { + element.addClass('app-hide'); + } + else { + var targetAndPermissions = []; + + if(angular.isArray(permissionCode)) { + _.each(permissionCode, function(item) { + targetAndPermissions.push({ + targetType: 'PKG', + targetIdentifier : pkg.name, + permissionCode : item + }); + }); + } + else { + targetAndPermissions.push({ + targetType: 'PKG', + targetIdentifier : pkg.name, + permissionCode : permissionCode + }); + } ++ userState.areAuthorized(targetAndPermissions).then(function(flag) {
+ if(flag) { + element.removeClass('app-hide'); + } + else { + element.addClass('app-hide'); + } + }); + } + } + + } + } + } +]); ======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifrepositorypermissiondirective.js Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,75 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +/**+ * <p>This directive is able to display the transcluded (material inside the element) if the permission holds against
+ * the nominated repository.</p> + */ + +angular.module('haikudepotserver').directive('showIfRepositoryPermission',[ + 'userState', function(userState) { + return { + restrict: 'A', + link : function($scope,element,attributes) { + + var repositoryExpression = attributes['repository'];+ var permissionCodeExpression = attributes['showIfRepositoryPermission'];
+ var repository = $scope.$eval(repositoryExpression);+ var permissionCode = $scope.$eval(permissionCodeExpression);
+ + // by default we will hide it. + + element.addClass('app-hide'); + check(); ++ $scope.$watch(repositoryExpression, function(newValue,oldValue) {
+ repository = newValue; + check(); + }); ++ $scope.$watch(permissionCodeExpression, function(newValue,oldValue) {
+ permissionCode = newValue; + check(); + }); + + function check() { + if(!permissionCode || !repository) { + element.addClass('app-hide'); + } + else { + var targetAndPermissions = []; + + if(angular.isArray(permissionCode)) { + _.each(permissionCode, function(item) { + targetAndPermissions.push({ + targetType: 'REPOSITORY', + targetIdentifier : repository.code, + permissionCode : item + }); + }); + } + else { + targetAndPermissions.push({ + targetType: 'REPOSITORY', + targetIdentifier : repository.code, + permissionCode : permissionCode + }); + } ++ userState.areAuthorized(targetAndPermissions).then(function(flag) {
+ if(flag) { + element.removeClass('app-hide'); + } + else { + element.addClass('app-hide'); + } + }); + } + } + + } + } + } +]); ======================================= --- /dev/null+++ /haikudepotserver-webapp/src/test/resources/logback.xml Sat Feb 8 09:19:06 2014 UTC
@@ -0,0 +1,22 @@ +<configuration> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <!-- encoders are assigned the type+ ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
+ <encoder>+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder> + </appender> + + <!--+ Cayenne logs quite a bit of material out; best to turn that quantity of logging down a bit.
+ --> + + <logger name="org.apache.cayenne" level="warn"/> + <logger name="com.googlecode.flyway" level="info"/> + + <root level="info"> + <appender-ref ref="STDOUT" /> + </root> + +</configuration> =======================================--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java Sat Feb 8 09:19:06 2014 UTC
@@ -11,6 +11,13 @@ @JsonRpcService("/api/v1/miscellaneous") public interface MiscellaneousApi { + /**+ * <p>This method will take in a list of permissions with targets and will return the list of those that + * pass authorization checks against the presently authenticated user.</p>
+ */ ++ CheckAuthorizationResult checkAuthorization(CheckAuthorizationRequest deriveAuthorizationRequest);
+ /*** <p>This method will raise a runtime exception to test the behaviour of the server and client in this
* situation.</p> =======================================--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java Sat Feb 8 09:19:06 2014 UTC
@@ -6,8 +6,8 @@ package org.haikuos.haikudepotserver.api1; import com.googlecode.jsonrpc4j.JsonRpcService;-import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesRequest; -import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesResult;
+import org.haikuos.haikudepotserver.api1.model.repository.*; +import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException; @JsonRpcService("/api/v1/repository") public interface RepositoryApi { @@ -19,4 +19,24 @@SearchRepositoriesResult searchRepositories(SearchRepositoriesRequest searchRepositoriesRequest);
+ /**+ * <p>This method will return the repository details for the repository identified by the
+ * code in the request object.</p> + */ ++ GetRepositoryResult getRepository(GetRepositoryRequest getRepositoryRequest) throws ObjectNotFoundException;
+ + /**+ * <p>This method will update the repository. As well as the data to update, it also includes a 'filter' that
+ * defines the fields that should be updated in this request.</p> + */ ++ UpdateRepositoryResult updateRepository(UpdateRepositoryRequest updateRepositoryRequest) throws ObjectNotFoundException;
+ + /** + * <p>This method will trigger the import process for a repository.</p> + */ ++ TriggerImportRepositoryResult triggerImportRepositoryResult(TriggerImportRepositoryRequest triggerImportRepositoryRequest) throws ObjectNotFoundException;
+ } =======================================--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java Thu Dec 5 09:23:22 2013 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java Sat Feb 8 09:19:06 2014 UTC
@@ -28,13 +28,6 @@ public Boolean hasIcon; - /**- * <p>This field will be true if the user who is presently authenticated would be able to
- * edit the package.</p> - */ - - public Boolean canEdit; - public List<Version> versions; public static class Version { =======================================--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesRequest.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesRequest.java Sat Feb 8 09:19:06 2014 UTC
@@ -8,4 +8,7 @@ import org.haikuos.haikudepotserver.api1.support.AbstractSearchRequest; public class SearchRepositoriesRequest extends AbstractSearchRequest { + + public Boolean includeInactive; + } =======================================--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesResult.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesResult.java Sat Feb 8 09:19:06 2014 UTC
@@ -13,10 +13,7 @@ public Boolean active; public String code; - public String url; public String architectureCode; - public Long createTimestamp; - public Long modifyTimestamp; } ======================================= --- /haikudepotserver-parent/pom.xml Sat Jan 18 09:59:17 2014 UTC +++ /haikudepotserver-parent/pom.xml Sat Feb 8 09:19:06 2014 UTC @@ -47,7 +47,7 @@ <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> - <version>15.0</version> + <version>16.0.1</version> </dependency> <!-- WEB / API --> =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApiImpl.java Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApiImpl.java Sat Feb 8 09:19:06 2014 UTC
@@ -5,18 +5,18 @@ package org.haikuos.haikudepotserver.api1; -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; -import com.google.common.base.Predicate; +import com.google.common.base.*; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.configuration.server.ServerRuntime; +import org.haikuos.haikudepotserver.security.model.Permission; import org.haikuos.haikudepotserver.api1.model.miscellaneous.*; import org.haikuos.haikudepotserver.dataobjects.Architecture; import org.haikuos.haikudepotserver.dataobjects.User; +import org.haikuos.haikudepotserver.security.AuthorizationService; +import org.haikuos.haikudepotserver.security.model.TargetType; import org.haikuos.haikudepotserver.support.Closeables; import org.haikuos.haikudepotserver.support.RuntimeInformationService; import org.slf4j.Logger; @@ -40,8 +40,42 @@ @Resource ServerRuntime serverRuntime; + @Resource + AuthorizationService authorizationService; + @Resource RuntimeInformationService runtimeInformationService; + + @Override+ public CheckAuthorizationResult checkAuthorization(CheckAuthorizationRequest deriveAuthorizationRequest) {
+ + Preconditions.checkNotNull(deriveAuthorizationRequest);+ Preconditions.checkNotNull(deriveAuthorizationRequest.targetAndPermissions);
+ + final ObjectContext context = serverRuntime.getContext(); + CheckAuthorizationResult result = new CheckAuthorizationResult(); + result.targetAndPermissions = Lists.newArrayList(); ++ for(CheckAuthorizationRequest.AuthorizationTargetAndPermission targetAndPermission : deriveAuthorizationRequest.targetAndPermissions) {
++ CheckAuthorizationResult.AuthorizationTargetAndPermission authorizationTargetAndPermission = new CheckAuthorizationResult.AuthorizationTargetAndPermission();
++ authorizationTargetAndPermission.permissionCode = targetAndPermission.permissionCode; + authorizationTargetAndPermission.targetIdentifier = targetAndPermission.targetIdentifier; + authorizationTargetAndPermission.targetType = targetAndPermission.targetType;
++ authorizationTargetAndPermission.authorized = authorizationService.check(
+ context, + tryObtainAuthenticatedUser(context).orNull(),+ null!=targetAndPermission.targetType ? TargetType.valueOf(targetAndPermission.targetType.name()) : null,
+ targetAndPermission.targetIdentifier,+ Permission.valueOf(targetAndPermission.permissionCode));
++ result.targetAndPermissions.add(authorizationTargetAndPermission);
+ } + + return result; + } @Overridepublic RaiseExceptionResult raiseException(RaiseExceptionRequest raiseExceptionRequest) {
=======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Sat Feb 8 09:19:06 2014 UTC
@@ -18,9 +18,11 @@ import org.haikuos.haikudepotserver.api1.model.pkg.*;import org.haikuos.haikudepotserver.api1.support.AuthorizationFailureException;
import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException; +import org.haikuos.haikudepotserver.security.model.Permission; import org.haikuos.haikudepotserver.dataobjects.*; import org.haikuos.haikudepotserver.pkg.PkgService; import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification; +import org.haikuos.haikudepotserver.security.AuthorizationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -41,6 +43,9 @@ @Resource ServerRuntime serverRuntime; + @Resource + AuthorizationService authorizationService; + @Resource PkgService pkgService; @@ -208,7 +213,6 @@ result.name = pkgOptional.get().getName();result.modifyTimestamp = pkgOptional.get().getModifyTimestamp().getTime(); - result.canEdit = pkgOptional.get().canBeEditedBy(tryObtainAuthenticatedUser(context).orNull());
result.hasIcon = !pkgOptional.get().getPkgIcons().isEmpty(); switch(request.versionType) { @@ -257,8 +261,8 @@ User user = obtainAuthenticatedUser(context); - if(!pkgOptional.get().canBeEditedBy(user)) {- logger.warn("attempt to remove the icon for package {}, but the user {} is not able to",pkgOptional.get().getName(),user.getNickname()); + if(!authorizationService.check(context, user, pkgOptional.get(), Permission.PKG_EDITICON)) { + logger.warn("attempt to remove the icon for package {}, but the user {} is not able to", pkgOptional.get().getName(), user.getNickname());
throw new AuthorizationFailureException(); } =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sat Feb 8 09:19:06 2014 UTC
@@ -6,37 +6,104 @@ package org.haikuos.haikudepotserver.api1; import com.google.common.base.Function; +import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.configuration.server.ServerRuntime;-import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesRequest; -import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesResult;
+import org.haikuos.haikudepotserver.api1.model.repository.*;+import org.haikuos.haikudepotserver.api1.support.AuthorizationFailureException;
+import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException; import org.haikuos.haikudepotserver.dataobjects.Repository; +import org.haikuos.haikudepotserver.dataobjects.User; +import org.haikuos.haikudepotserver.pkg.model.PkgRepositoryImportJob; import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification; +import org.haikuos.haikudepotserver.repository.RepositoryImportService; import org.haikuos.haikudepotserver.repository.RepositoryService;import org.haikuos.haikudepotserver.repository.model.RepositorySearchSpecification;
+import org.haikuos.haikudepotserver.security.AuthorizationService; +import org.haikuos.haikudepotserver.security.model.Permission; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import javax.naming.ldap.PagedResultsControl; import java.util.List; @Componentpublic class RepositoryApiImpl extends AbstractApiImpl implements RepositoryApi {
+ protected static Logger logger = LoggerFactory.getLogger(RepositoryApiImpl.class);
+ @Resource ServerRuntime serverRuntime; + @Resource + AuthorizationService authorizationService; + @Resource RepositoryService repositoryService; + @Resource + RepositoryImportService repositoryImportService; + + // note; no integration test for this one. + @Override + public TriggerImportRepositoryResult triggerImportRepositoryResult( + TriggerImportRepositoryRequest triggerImportRepositoryRequest) + throws ObjectNotFoundException { + + Preconditions.checkNotNull(triggerImportRepositoryRequest);+ Preconditions.checkState(!Strings.isNullOrEmpty(triggerImportRepositoryRequest.code));
+ + final ObjectContext context = serverRuntime.getContext(); ++ Optional<Repository> repositoryOptional = Repository.getByCode(context, triggerImportRepositoryRequest.code);
+ + if(!repositoryOptional.isPresent()) {+ throw new ObjectNotFoundException(Repository.class.getSimpleName(), triggerImportRepositoryRequest.code);
+ } + + if(!authorizationService.check( + context, + tryObtainAuthenticatedUser(context).orNull(), + repositoryOptional.get(), + Permission.REPOSITORY_IMPORT)) { + throw new AuthorizationFailureException(); + } ++ repositoryImportService.submit(new PkgRepositoryImportJob(repositoryOptional.get().getCode()));
+ + return new TriggerImportRepositoryResult(); + } + + @Overridepublic SearchRepositoriesResult searchRepositories(SearchRepositoriesRequest request) {
Preconditions.checkNotNull(request); final ObjectContext context = serverRuntime.getContext(); + + if(!authorizationService.check( + context, + tryObtainAuthenticatedUser(context).orNull(), + null, + Permission.REPOSITORY_LIST)) { + throw new AuthorizationFailureException(); + } + + if(null!=request.includeInactive && request.includeInactive) { + if(!authorizationService.check( + context, + tryObtainAuthenticatedUser(context).orNull(), + null, + Permission.REPOSITORY_LIST_INACTIVE)) { + throw new AuthorizationFailureException(); + } + }RepositorySearchSpecification specification = new RepositorySearchSpecification();
String exp = request.expression; @@ -54,6 +121,7 @@specification.setLimit(request.limit+1); // get +1 to see if there are any more.
specification.setOffset(request.offset);+ specification.setIncludeInactive(null!=request.includeInactive && request.includeInactive);
SearchRepositoriesResult result = new SearchRepositoriesResult();List<Repository> searchedRepositories = repositoryService.search(context,specification);
@@ -75,8 +143,6 @@ resultRepository.active = input.getActive();resultRepository.architectureCode = input.getArchitecture().getCode();
resultRepository.code = input.getCode();- resultRepository.createTimestamp = input.getCreateTimestamp().getTime(); - resultRepository.modifyTimestamp = input.getModifyTimestamp().getTime();
return resultRepository; } } @@ -84,5 +150,84 @@ return result; } + + @Override+ public GetRepositoryResult getRepository(GetRepositoryRequest getRepositoryRequest) throws ObjectNotFoundException {
+ Preconditions.checkNotNull(getRepositoryRequest);+ Preconditions.checkState(!Strings.isNullOrEmpty(getRepositoryRequest.code));
+ + final ObjectContext context = serverRuntime.getContext(); ++ Optional<Repository> repositoryOptional = Repository.getByCode(context, getRepositoryRequest.code);
+ + if(!repositoryOptional.isPresent()) {+ throw new ObjectNotFoundException(Repository.class.getSimpleName(), getRepositoryRequest.code);
+ } + + if(!authorizationService.check( + context, + tryObtainAuthenticatedUser(context).orNull(), + repositoryOptional.get(), + Permission.REPOSITORY_VIEW)) { + throw new AuthorizationFailureException(); + } + + GetRepositoryResult result = new GetRepositoryResult(); + result.active = repositoryOptional.get().getActive();+ result.architectureCode = repositoryOptional.get().getArchitecture().getCode();
+ result.code = repositoryOptional.get().getCode();+ result.createTimestamp = repositoryOptional.get().getCreateTimestamp().getTime(); + result.modifyTimestamp = repositoryOptional.get().getModifyTimestamp().getTime();
+ result.url = repositoryOptional.get().getUrl(); + + return result; + } + + @Override+ public UpdateRepositoryResult updateRepository(UpdateRepositoryRequest updateRepositoryRequest) throws ObjectNotFoundException {
+ Preconditions.checkNotNull(updateRepositoryRequest); + + final ObjectContext context = serverRuntime.getContext(); ++ Optional<Repository> repositoryOptional = Repository.getByCode(context, updateRepositoryRequest.code);
+ + if(!repositoryOptional.isPresent()) {+ throw new ObjectNotFoundException(Repository.class.getSimpleName(), updateRepositoryRequest.code);
+ } + + authorizationService.check( + context, + tryObtainAuthenticatedUser(context).orNull(), + repositoryOptional.get(), + Permission.REPOSITORY_EDIT); ++ for(UpdateRepositoryRequest.Filter filter : updateRepositoryRequest.filter) {
+ switch(filter) { + case ACTIVE: + if(null==updateRepositoryRequest.active) {+ throw new IllegalStateException("the active flag must be supplied");
+ } ++ if(repositoryOptional.get().getActive() != updateRepositoryRequest.active) { + repositoryOptional.get().setActive(updateRepositoryRequest.active); + logger.info("did set the active flag on repository {} to {}", updateRepositoryRequest.code,updateRepositoryRequest.active);
+ } + + break; + + default:+ throw new IllegalStateException("unhandled filter for updating a repository");
+ } + } + + if(context.hasChanges()) { + context.commitChanges(); + } + else {+ logger.info("update repository {} with no changes made", updateRepositoryRequest.code);
+ } + + return new UpdateRepositoryResult(); + } } =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Sat Feb 8 09:19:06 2014 UTC
@@ -11,11 +11,13 @@ import com.google.common.util.concurrent.Uninterruptibles; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.configuration.server.ServerRuntime; +import org.haikuos.haikudepotserver.security.model.Permission; import org.haikuos.haikudepotserver.api1.model.user.*; import org.haikuos.haikudepotserver.api1.support.*; import org.haikuos.haikudepotserver.captcha.CaptchaService; import org.haikuos.haikudepotserver.dataobjects.User; import org.haikuos.haikudepotserver.security.AuthenticationService; +import org.haikuos.haikudepotserver.security.AuthorizationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -31,6 +33,9 @@ @Resource ServerRuntime serverRuntime; + @Resource + AuthorizationService authorizationService; + @Resource CaptchaService captchaService; @@ -96,15 +101,15 @@ final ObjectContext context = serverRuntime.getContext(); User authUser = obtainAuthenticatedUser(context);- if(!authUser.getNickname().equals(getUserRequest.nickname) && !authUser.getDerivedCanManageUsers()) {
- throw new AuthorizationFailureException(); - } -Optional<User> user = User.getByNickname(context, getUserRequest.nickname);
if(!user.isPresent()) {throw new ObjectNotFoundException(User.class.getSimpleName(), User.NICKNAME_PROPERTY);
} ++ if(!authorizationService.check(context, authUser, user.get(), Permission.USER_VIEW)) {
+ throw new AuthorizationFailureException(); + } GetUserResult result = new GetUserResult(); result.nickname = user.get().getNickname(); @@ -176,18 +181,6 @@ throw new CaptchaBadResponseException(); } } -- // if the logged in user is not root then only the user who has authenticated can change their password.
-- if(!authUser.getNickname().equals(changePasswordRequest.nickname)) {
- if(authUser.getIsRoot()) { - logger.info("allowing change password for root user"); - } - else {- logger.info("the logged in user {} is not allowed to change the password of another user {}",authUser.getNickname(),changePasswordRequest.nickname);
- throw new AuthorizationFailureException(); - } - }// if the logged in user is non-root then we need to make sure that the old and new passwords
// match-up. @@ -218,6 +211,12 @@ } User user = userOptional.get(); ++ if(!authorizationService.check(context, authUser, userOptional.get(), Permission.USER_CHANGEPASSWORD)) { + logger.info("the logged in user {} is not allowed to change the password of another user {}",authUser.getNickname(),changePasswordRequest.nickname);
+ throw new AuthorizationFailureException(); + } +user.setPasswordHash(authenticationService.hashPassword(user, changePasswordRequest.newPasswordClear));
context.commitChanges();logger.info("did change password for user {}", changePasswordRequest.nickname);
=======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Pkg.java Mon Jan 20 10:45:32 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Pkg.java Sat Feb 8 09:19:06 2014 UTC
@@ -76,10 +76,6 @@ return Optional.absent(); } - - public boolean canBeEditedBy(User user) { - return null!=user && user.getIsRoot(); - } public void setModifyTimestamp() { setModifyTimestamp(new java.util.Date()); =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java Sat Feb 8 09:19:06 2014 UTC
@@ -1,5 +1,5 @@ /* - * Copyright 2013, Andrew Lindesay + * Copyright 2013-2014, Andrew Lindesay * Distributed under the terms of the MIT License. */ @@ -11,6 +11,8 @@ import com.google.common.net.MediaType; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.configuration.server.ServerRuntime; +import org.haikuos.haikudepotserver.security.model.Permission; +import org.haikuos.haikudepotserver.security.AuthorizationService; import org.haikuos.haikudepotserver.support.ByteCounterOutputStream; import org.haikuos.haikudepotserver.support.NoOpOutputStream;import org.haikuos.haikudepotserver.web.controller.WebResourceGroupController;
@@ -53,6 +55,9 @@ @Resource PkgService pkgService; + @Resource + AuthorizationService authorizationService; +@RequestMapping(value = "/{"+KEY_PKGNAME+"}.{"+KEY_FORMAT+"}", method = RequestMethod.HEAD)
public void fetchHead( HttpServletRequest request, @@ -175,7 +180,7 @@ Optional<User> user = tryObtainAuthenticatedUser(context); - if(!user.isPresent() || !pkg.get().canBeEditedBy(user.get())) {+ if(!authorizationService.check(context, user.orNull(), pkg.get(), Permission.PKG_EDITICON)) { logger.warn("attempt to edit the pkg icon, but there is no user present or that user is not able to edit the pkg");
throw new PkgAuthorizationFailure(); } =======================================--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sat Feb 8 09:19:06 2014 UTC
@@ -59,6 +59,9 @@<value>/js/app/directive/userlabeldirective.js</value> <value>/js/app/directive/repositorylabeldirective.js</value> <value>/js/app/directive/activeindicatordirective.js</value> + <value>/js/app/directive/showifpkgpermissiondirective.js</value> + <value>/js/app/directive/showifrepositorypermissiondirective.js</value> + <value>/js/app/directive/showifpermissiondirective.js</value>
<value>/js/app/controller/viewpkgcontroller.js</value> <value>/js/app/controller/homecontroller.js</value>
@@ -71,6 +74,7 @@<value>/js/app/controller/morecontroller.js</value> <value>/js/app/controller/runtimeinformationcontroller.js</value> <value>/js/app/controller/listrepositoriescontroller.js</value> + <value>/js/app/controller/viewrepositorycontroller.js</value>
<value>/js/app/service/jsonrpcservice.js</value> <value>/js/app/service/messagesourceservice.js</value>
@@ -99,6 +103,7 @@ <value>/css/haikudepotserver.css</value> <value>/css/home.css</value> <value>/css/createuser.css</value> + <value>/css/listrepositories.css</value> <value>/css/viewpkg.css</value> <value>/css/banner.css</value> <value>/css/breadcrumbs.css</value> =======================================--- /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Sat Feb 8 09:19:06 2014 UTC
@@ -337,20 +337,43 @@ /* ================================== -ACTIVE / INACTIVE INDICATORS +ACTIVE / INACTIVE INDICATORS -- SVG */ -.active-indicator { - background-color: grey; - width: 12px; - height: 12px; - border-radius: 6px; +.active-indicator.active-indicator-true { + fill: darkolivegreen; +} + +.active-indicator.active-indicator-false { + fill: firebrick; +} + +/* +================================== +DATA LISTS +*/ + +dl dt { + color:gray; + float:left; + clear:left; + text-align: right; + margin-right:10px; + padding:5px; + width:20%; } -.active-indicator.active-indicator-true { - background-color: darkolivegreen; +dl dd { + margin:2px 0; + padding:5px 0; + width:80%; } -.inactive-indicator.active-indicator-false { - background-color: firebrick; +/* +================================== +HIDE / SHOW FOR OWN DIRECTIVES +*/ + +.app-hide { + display: none; } =======================================--- /haikudepotserver-webapp/src/main/webapp/css/home.css Sun Feb 2 09:38:27 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/home.css Sat Feb 8 09:19:06 2014 UTC
@@ -2,15 +2,15 @@This specific layout works off the premise that there is only one table on this page.
*/ -.table-general > tbody > tr > td:nth-child(1) { +.home .table-general > tbody > tr > td:nth-child(1) { width: 24px; } -.table-general > tbody > tr > td:nth-child(1) { +.home .table-general > tbody > tr > td:nth-child(1) { width:18px; text-align: center; } -.table-general > tbody > tr > td:nth-child(2) { +.home .table-general > tbody > tr > td:nth-child(2) { width:50%; } =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js Sat Feb 8 09:19:06 2014 UTC
@@ -118,7 +118,15 @@ function() { $scope.amSaving = false;$log.info('have set the 32px icon for the pkg '+$scope.pkg.name); - $location.path('/viewpkg/'+$scope.pkg.name+'/latest').search({});
+ var arch = $location.search()['arch']; + + if(arch) {+ $location.path('/viewpkg/'+$scope.pkg.name+'/latest/'+arch).search({});
+ } + else {+ $log.info('unable to navigate back to the pkg as no \'arch\' was supplied in the search parameters --> will go home');
+ $location.path('/').search({}); + } }, function(e) { $scope.amSaving = false; =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html Sat Feb 8 09:19:06 2014 UTC
@@ -1,4 +1,4 @@ -<div class="content-container"> +<div class="content-container home"> <div id="search-criteria-container"> <div> =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html Sat Feb 8 09:19:06 2014 UTC
@@ -1,6 +1,6 @@ <breadcrumbs items="breadcrumbItems"></breadcrumbs> -<div class="content-container"> +<div class="content-container list-repositories"> <div id="search-criteria-container"> <div> @@ -32,21 +32,28 @@ <table class="table-general"> <thead> + <th>Active</th> <th>Code</th> <th>Architecture</th> - <th>Active</th> </thead> <tbody> <tr ng-repeat="repository in repositories">+ <td><active-indicator state="repository.active"></active-indicator></td> <td><repository-label repository="repository"></repository-label></td>
<td><code>{{repository.architectureCode}}</code></td>- <td><active-indicator state="repository.active"></active-indicator></td>
</tr> </tbody> </table> </div> </div> + + <ul>+ <li ng-show="!amShowingInactive" show-if-permission="'REPOSITORY_LIST_INACTIVE'"> + <a href="" ng-click="goShowInactive()">Show inactive repositories</a>
+ </li> + </ul> + </div> <div class="footer"></div> =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js Sat Feb 8 09:19:06 2014 UTC
@@ -24,14 +24,19 @@ $scope.repositories = undefined; $scope.hasMore = undefined; $scope.offset = 0; + $scope.amShowingInactive = false; + var amFetchingRepositories = false; refetchRepositoriesAtFirstPage(); - var amFetchingRepositories = false; - $scope.shouldSpin = function() { return amFetchingRepositories; } + + $scope.goShowInactive = function() { + $scope.amShowingInactive = true; + refetchRepositoriesAtFirstPage(); + } // ---- PAGINATION @@ -55,6 +60,7 @@ [{ expression : $scope.searchExpression, expressionType : 'CONTAINS', + includeInactive : $scope.amShowingInactive, offset : $scope.offset, limit : PAGESIZE }] =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/more.html Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/more.html Sat Feb 8 09:19:06 2014 UTC
@@ -14,7 +14,7 @@ <li> <a href="" ng-click="goHome()">Home</a> </li> - <li> + <li show-if-permission="'REPOSITORY_LIST'"> <a href="" ng-click="goListRepositories()">Repositories</a> </li> <li ng-show="canGoRuntimeInformation"> =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Sat Feb 8 09:19:06 2014 UTC
@@ -43,12 +43,14 @@<h2 ng-show="pkg.versions[0].summary" class="lead">{{pkg.versions[0].summary}}</h2> <p ng-show="pkg.versions[0].description">{{pkg.versions[0].description}}</p>
- <h2>Actions</h2> - <ul><li><a target="_blank" ng-show="homePageLink()" href="{{homePageLink()}}">Visit web site</a></li> - <li ng-show="canRemoveIcon()"><a href="" ng-click="goRemoveIcon()">Remove icon</a></li> - <li ng-show="canEditIcon()"><a href="" ng-click="goEditIcon()">Edit icons</a></li> + <li ng-show="canRemoveIcon()" pkg="pkg" show-if-pkg-permission="'PKG_EDITICON'">
+ <a href="" ng-click="goRemoveIcon()">Remove icon</a> + </li>+ <li ng-show="canEditIcon()" pkg="pkg" show-if-pkg-permission="'PKG_EDITICON'">
+ <a href="" ng-click="goEditIcon()">Edit icons</a> + </li> </ul> </div> =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Sat Feb 8 09:19:06 2014 UTC
@@ -22,11 +22,11 @@ } $scope.canRemoveIcon = function() {- return $scope.pkg && $scope.pkg.canEdit && $scope.pkg.hasIcon;
+ return $scope.pkg && $scope.pkg.hasIcon; } $scope.canEditIcon = function() { - return $scope.pkg && $scope.pkg.canEdit; + return $scope.pkg; } $scope.homePageLink = function() { @@ -75,7 +75,7 @@ } $scope.goEditIcon = function() { - $location.path("/editpkgicon/"+$scope.pkg.name).search({});+ $location.path("/editpkgicon/"+$scope.pkg.name).search({'arch':$routeParams.architectureCode});
} $scope.goRemoveIcon = function() { =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/activeindicatordirective.js Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/activeindicatordirective.js Sat Feb 8 09:19:06 2014 UTC
@@ -11,7 +11,7 @@ angular.module('haikudepotserver').directive('activeIndicator',function() { return { restrict: 'E', - template:'<div ng-class="classes"> </div>', + templateUrl:'/js/app/directive/activeindicator.html', replace: true, scope: { state: '=' @@ -25,7 +25,7 @@ $scope.$watch('state',function(newValue, oldValue) { $scope.classes = [ 'active-indicator',- newValue ? 'active-indicator-true' : 'inactive-indicator-false' + newValue ? 'active-indicator-true' : 'active-indicator-false'
]; }); =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js Sun Feb 2 09:38:27 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js Sat Feb 8 09:19:06 2014 UTC
@@ -10,14 +10,18 @@ angular.module('haikudepotserver').directive('repositoryLabel',function() { return { restrict: 'E', - template:'<span>{{repository.code}}</span>',+ template:'<a href=\"\" ng-click=\"goView()\">{{repository.code}}</a>',
replace: true, scope: { repository: '=' }, controller: - ['$scope', - function($scope) { + ['$scope','$location', + function($scope,$location) { + + $scope.goView = function() {+ $location.path('/viewrepository/'+$scope.repository.code).search({});
+ } } ] }; =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Sat Feb 8 09:19:06 2014 UTC
@@ -8,6 +8,7 @@ '$routeProvider', function($routeProvider) { $routeProvider+ .when('/viewrepository/:code',{controller:'ViewRepositoryController', templateUrl:'/js/app/controller/viewrepository.html'}) .when('/listrepositories',{controller:'ListRepositoriesController', templateUrl:'/js/app/controller/listrepositories.html'}) .when('/runtimeinformation',{controller:'RuntimeInformationController', templateUrl:'/js/app/controller/runtimeinformation.html'}) .when('/more',{controller:'MoreController', templateUrl:'/js/app/controller/more.html'})
=======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Sat Feb 8 09:19:06 2014 UTC
@@ -13,6 +13,13 @@ var BreadcrumbsService = { + createViewRepository : function(repository) { + return { + title : repository.code, + path : '/viewrepository/' + repository.code + } + }, + createListRepositories : function() { return { title : 'List Repositories', =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Sun Feb 2 09:38:27 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Sat Feb 8 09:19:06 2014 UTC
@@ -13,21 +13,26 @@ var ErrorHandlingService = { - /**- * <p>When a JSON-RPC failure occurs, this method can be invoked to provide uniform logging and
- * handling.</p> - */ + logJsonRpcError : function(jsonRpcErrorEnvelope, message) {+ var prefix = message ? message + ' - json-rpc error; ' : 'json-rpc error; ';
- handleJsonRpcError : function(jsonRpcErrorEnvelope) { if(null==jsonRpcErrorEnvelope) {- $log.error('json-rpc error; cause is unknown as no error envelope was available'); + $log.error(prefix+'cause is unknown as no error envelope was available');
} else {var code = jsonRpcErrorEnvelope.code ? jsonRpcErrorEnvelope.code : '?'; var message = jsonRpcErrorEnvelope.message ? jsonRpcErrorEnvelope.message : '?'; - $log.error('json-rpc error; code:'+code+", msg:"+message);
+ $log.error(prefix+'code:'+code+", msg:"+message); } + }, + + /**+ * <p>When a JSON-RPC failure occurs, this method can be invoked to provide uniform logging and
+ * handling.</p> + */ + handleJsonRpcError : function(jsonRpcErrorEnvelope) {+ ErrorHandlingService.logJsonRpcError(jsonRpcErrorEnvelope);
$location.path("/error").search({}); }, =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Sat Feb 1 07:54:02 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Sat Feb 8 09:19:06 2014 UTC
@@ -7,14 +7,185 @@* <p>This service is here to maintain the current user's state. When the user logs in for example, this is stored * here. This service may take other actions such as configuring headers in the jsonRpc service when the user logs-in
* or logs-out.</p> + *+ * <p>This service also manages authorization information. There is the possibility to "check" on a permission.</p>
*/ angular.module('haikudepotserver').factory('userState', [ - '$log','$q','$rootScope','jsonRpc','pkgIcon', - function($log,$q,$rootScope,jsonRpc,pkgIcon) { + '$log','$q','$rootScope','jsonRpc','pkgIcon','errorHandling','constants',+ function($log,$q,$rootScope,jsonRpc,pkgIcon,errorHandling,constants) {
+ + const SIZE_CHECKED_PERMISSION_CACHE = 25; var user = undefined; + var checkedPermissionCache = []; + var checkQueue = []; + + function validateTargetAndPermissions(targetAndPermissions) {+ _.each(targetAndPermissions, function(targetAndPermission) { + if(undefined === targetAndPermission.targetType | | !_.contains(['PKG','USER','REPOSITORY',null],targetAndPermission.targetType)) {
+ throw 'illegal argument; bad targetType supplied'; + } ++ if(undefined === targetAndPermission.targetIdentifier) { + throw 'illegal argument; bad targetIdentifier supplied';
+ } + + if(!targetAndPermission.permissionCode) { + throw 'illegal argument; bad permission code'; + } + }) + } + + function resetAuthorization() { + checkedPermissionCache = []; + checkQueue = []; + } + + function check(targetAndPermissions) { ++ // this function will see if it is able to resolve the top-most item in the queue by just looking + // in the cache. This function will return the result if there is one from cache; otherwise it
+ // will return null. ++ function tryDeriveFromCache(targetAndPermissionsToCheckAgainstCache) {
+ + if(!targetAndPermissionsToCheckAgainstCache.length) { + return null; + } + + if(!checkedPermissionCache.length) { + return null; + } + + var result = []; ++ for(var i=0;i<targetAndPermissionsToCheckAgainstCache.length;i++) {
+ var cachedTargetAndPermission = _.findWhere( + checkedPermissionCache, + targetAndPermissionsToCheckAgainstCache[i]); + + if(!cachedTargetAndPermission) { + return null; + } + else { + result.push(cachedTargetAndPermission); + } + } + + return result; + } ++ // this function will take the item from the queue and handle it either by looking in the + // local cache or by talking to the remote application server.
+ + function handleNextInCheckQueue() { + if(checkQueue.length) { + var request = checkQueue[0];+ var result = tryDeriveFromCache(request.targetAndPermissions);
+ + if(null!=result) { + request.deferred.resolve(result); + checkQueue.shift(); + handleNextInCheckQueue(); + } + else { ++ // we will have to go off to the application server to get the authorizations that we need
+ + var uncachedTargetAndPermissions = _.filter( + request.targetAndPermissions, + function(targetAndPermission) {+ return !_.findWhere(checkedPermissionCache, targetAndPermission);
+ } + ); + + // TODO - might be faster?+ // if we have not exceeded the cache size, it would make sense to blend in a few more + // up-coming requests' data at the same time so that they will also be cached too. We + // might be able to be a bit smarter about this in the future.
+ + if(!uncachedTargetAndPermissions.length) {+ throw 'illegal state; top-most request has no uncached target and permissions';
+ } + + jsonRpc.call(+ constants.ENDPOINT_API_V1_MISCELLANEOUS,
+ 'checkAuthorization',+ [{ targetAndPermissions : uncachedTargetAndPermissions }]
+ ).then( + function(data) { ++ // blend the new material into the cache.
++ checkedPermissionCache = data.targetAndPermissions.concat(checkedPermissionCache);
++ // we should now be in a position to resolve the permission from the cache.
++ result = tryDeriveFromCache(request.targetAndPermissions);
+ + if(!result) {+ throw 'illegal state; was not able to resolve the request from cache after fetching from application server';
+ } + + request.deferred.resolve(result); ++ // now cull the cache so that we're not storing too much material. This is very
+ // simplistic; no LRU or anything. ++ if(checkedPermissionCache.length > SIZE_CHECKED_PERMISSION_CACHE) {
+ checkedPermissionCache.splice( + SIZE_CHECKED_PERMISSION_CACHE,+ checkedPermissionCache.length-SIZE_CHECKED_PERMISSION_CACHE);
+ } ++ // drop this request now that it has been dealt with and move onto checking the
+ // next one. + + checkQueue.shift(); + handleNextInCheckQueue(); + }, + function(err) { ++ // if there is a problem then treat it as 'fatal'
++ $log.error('a problem has arisen checking the authorization');
+ errorHandling.handleJsonRpcError(err); + request.deferred.reject(); + resetAuthorization(); + } + ); + + } + } + } + + var deferred = $q.defer(); + + // try handle this from the cached data. ++ var resultFromCache = tryDeriveFromCache(targetAndPermissions);
+ + if(resultFromCache) { + deferred.resolve(resultFromCache); + } + else { + + // push this request to the queue. + + checkQueue.push( { + deferred : deferred, + targetAndPermissions : targetAndPermissions + }); + + if(1==checkQueue.length) { + handleNextInCheckQueue(); + } + } + + return deferred.promise; + } var UserState = { @@ -29,6 +200,8 @@ $rootScope.$broadcast('userChangeStart',value); + resetAuthorization(); + if(null==value) { user = undefined; @@ -55,6 +228,7 @@ 'Basic '+window.btoa(''+value.nickname+':'+value.passwordClear)); user = value; + $log.info('have set user; '+user.nickname); } @@ -62,7 +236,52 @@ } return user; + }, + + // --------------------- + // AUTHORIZATION + + /**+ * <p>This function will check to make sure that the target and permissions supplied are authorized. + * The single argument should be an array of objects. Each object should have the following
+ * elements;</p> + * + * <ul> + * <li>targetType</li> + * <li>targetIdentifier</li> + * <li>permissionCode</li> + * </ul> + *+ * <p>Returned is a promise which resolves to a list of the requested permissions with an additional + * property "authorized" which can be either true or false.</p>
+ */ + + authorize : function(targetAndPermissions) { + validateTargetAndPermissions(targetAndPermissions); + return check(targetAndPermissions); + }, + + areAuthorized : function(targetAndPermissions) { + validateTargetAndPermissions(targetAndPermissions); + + var deferred = $q.defer(); + + check(targetAndPermissions).then( + function(data) {+ // now filter through and make sure everything is true.
+ deferred.resolve(!_.find(data, function(item) { + return !item.authorized; + })); + }, + function() {+ // error handling should already have been dealt with.
+ deferred.reject(); + } + ); + + return deferred.promise; } + }; =======================================--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/MiscelaneousApiIT.java Sat Jan 18 09:59:17 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/MiscelaneousApiIT.java Sat Feb 8 09:19:06 2014 UTC
@@ -8,13 +8,15 @@ import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import org.fest.assertions.Assertions; import org.haikuos.haikudepotserver.api1.MiscellaneousApi;-import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllArchitecturesRequest; -import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllArchitecturesResult; -import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllMessagesRequest; -import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllMessagesResult;
+import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType; +import org.haikuos.haikudepotserver.security.model.Permission; +import org.haikuos.haikudepotserver.api1.model.miscellaneous.*; +import org.haikuos.haikudepotserver.support.RuntimeInformationService; import org.haikuos.haikudepotsever.api1.support.AbstractIntegrationTest;+import org.haikuos.haikudepotsever.api1.support.IntegrationTestSupportService;
import org.junit.Test; import javax.annotation.Resource; @@ -23,6 +25,90 @@ @Resource MiscellaneousApi miscellaneousApi; + + @Resource + RuntimeInformationService runtimeInformationService; + + @Resource + IntegrationTestSupportService integrationTestSupportService; + + private void assertTargetAndPermission( + IntegrationTestSupportService.StandardTestData data,+ CheckAuthorizationResult.AuthorizationTargetAndPermission targetAndPermission,
+ boolean result) {+ Assertions.assertThat(targetAndPermission.permissionCode).isEqualTo(Permission.PKG_EDITICON.name()); + Assertions.assertThat(targetAndPermission.targetIdentifier).isEqualTo(data.pkg1.getName()); + Assertions.assertThat(targetAndPermission.targetType).isEqualTo(AuthorizationTargetType.PKG); + Assertions.assertThat(targetAndPermission.authorized).isEqualTo(result);
+ } + + @Test + public void checkAuthorizationRequest_asUnauthenticated() {+ IntegrationTestSupportService.StandardTestData data = integrationTestSupportService.createStandardTestData();
++ CheckAuthorizationRequest request = new CheckAuthorizationRequest();
+ request.targetAndPermissions = Lists.newArrayList(); ++ request.targetAndPermissions.add(new CheckAuthorizationRequest.AuthorizationTargetAndPermission(
+ AuthorizationTargetType.PKG, + data.pkg1.getName(), + Permission.PKG_EDITICON.name())); + + // ------------------------------------+ CheckAuthorizationResult result = miscellaneousApi.checkAuthorization(request);
+ // ------------------------------------ ++ Assertions.assertThat(result.targetAndPermissions.size()).isEqualTo(1); + assertTargetAndPermission(data, result.targetAndPermissions.get(0), false);
+ + } ++ // TODO : when some more sophisticated cases are available; implement some better tests
+ @Test + public void checkAuthorizationRequest_asRoot() {+ IntegrationTestSupportService.StandardTestData data = integrationTestSupportService.createStandardTestData();
+ + setAuthenticatedUserToRoot(); ++ CheckAuthorizationRequest request = new CheckAuthorizationRequest();
+ request.targetAndPermissions = Lists.newArrayList(); ++ request.targetAndPermissions.add(new CheckAuthorizationRequest.AuthorizationTargetAndPermission(
+ AuthorizationTargetType.PKG, + data.pkg1.getName(), + Permission.PKG_EDITICON.name())); + + // ------------------------------------+ CheckAuthorizationResult result = miscellaneousApi.checkAuthorization(request);
+ // ------------------------------------ ++ Assertions.assertThat(result.targetAndPermissions.size()).isEqualTo(1); + assertTargetAndPermission(data, result.targetAndPermissions.get(0), true);
+ + } + + @Test + public void getRuntimeInformation_asUnauthenticated() { + + // ------------------------------------+ GetRuntimeInformationResult result = miscellaneousApi.getRuntimeInformation(new GetRuntimeInformationRequest());
+ // ------------------------------------ + + Assertions.assertThat(result.javaVersion).isNull(); + } + + @Test + public void getRuntimeInformation_asRoot() { + + setAuthenticatedUserToRoot(); + + // ------------------------------------+ GetRuntimeInformationResult result = miscellaneousApi.getRuntimeInformation(new GetRuntimeInformationRequest());
+ // ------------------------------------ ++ Assertions.assertThat(result.javaVersion).isEqualTo(runtimeInformationService.getJavaVersion());
+ + } @Test public void testGetAllMessages() { =======================================--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/PkgApiIT.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/PkgApiIT.java Sat Feb 8 09:19:06 2014 UTC
@@ -69,7 +69,6 @@ Assertions.assertThat(result.hasIcon).isFalse(); Assertions.assertThat(result.name).isEqualTo("pkg1"); - Assertions.assertThat(result.canEdit).isFalse(); Assertions.assertThat(result.versions.size()).isEqualTo(1);Assertions.assertThat(result.versions.get(0).architectureCode).isEqualTo("x86");
Assertions.assertThat(result.versions.get(0).major).isEqualTo("1"); ======================================= ***Additional files exist in this changeset.*** ============================================================================== Revision: d02f16acd3fc Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sat Feb 8 09:30:39 2014 UTC Log: + add gui for triggering repository imports http://code.google.com/p/haiku-depot-web-app/source/detail?r=d02f16acd3fc Modified:/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js
=======================================--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java Sat Feb 8 09:30:39 2014 UTC
@@ -37,6 +37,6 @@ * <p>This method will trigger the import process for a repository.</p> */- TriggerImportRepositoryResult triggerImportRepositoryResult(TriggerImportRepositoryRequest triggerImportRepositoryRequest) throws ObjectNotFoundException; + TriggerImportRepositoryResult triggerImportRepository(TriggerImportRepositoryRequest triggerImportRepositoryRequest) throws ObjectNotFoundException;
} =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sat Feb 8 09:30:39 2014 UTC
@@ -17,7 +17,6 @@import org.haikuos.haikudepotserver.api1.support.AuthorizationFailureException;
import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException; import org.haikuos.haikudepotserver.dataobjects.Repository; -import org.haikuos.haikudepotserver.dataobjects.User; import org.haikuos.haikudepotserver.pkg.model.PkgRepositoryImportJob; import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification; import org.haikuos.haikudepotserver.repository.RepositoryImportService; @@ -52,7 +51,7 @@ // note; no integration test for this one. @Override - public TriggerImportRepositoryResult triggerImportRepositoryResult( + public TriggerImportRepositoryResult triggerImportRepository( TriggerImportRepositoryRequest triggerImportRepositoryRequest) throws ObjectNotFoundException { =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html Sat Feb 8 09:30:39 2014 UTC
@@ -2,6 +2,11 @@ <div class="content-container"> + <div class="info-container" ng-show="didTriggerImportRepository">+ <strong>Import triggered -</strong> the application will import the repository's data
+ soon. + </div> + <dl> <dt>Code</dt> <dd><code>{{repository.code}}</code> </dd> =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js Sat Feb 8 09:30:39 2014 UTC
@@ -6,14 +6,15 @@ angular.module('haikudepotserver').controller( 'ViewRepositoryController', [ - '$scope','$log','$location','$routeParams', + '$scope','$log','$location','$routeParams','$timeout', 'jsonRpc','constants','userState','errorHandling','breadcrumbs', function( - $scope,$log,$location,$routeParams, + $scope,$log,$location,$routeParams,$timeout, jsonRpc,constants,userState,errorHandling,breadcrumbs) { $scope.breadcrumbItems = undefined; $scope.repository = undefined; + $scope.didTriggerImportRepository = false; var amUpdatingActive = false; refetchRepository(); @@ -68,7 +69,23 @@ */ $scope.goTriggerImport = function() { + jsonRpc.call( + constants.ENDPOINT_API_V1_REPOSITORY, + "triggerImportRepository", + [{ code: $routeParams.code }] + ).then( + function(result) {+ $log.info('triggered import for repository; '+$scope.repository.code);
+ $scope.didTriggerImportRepository = true; + $timeout(function() { + $scope.didTriggerImportRepository = false; + }, 3000) + }, + function(err) { + errorHandling.handleJsonRpcError(err); + } + ); } function refreshBreadcrumbItems() { ============================================================================== Revision: b3fbcf57101d Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sat Feb 8 10:16:22 2014 UTC Log: + small changes from checks http://code.google.com/p/haiku-depot-web-app/source/detail?r=b3fbcf57101d Added:/haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabel.html
Modified:/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js
======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabel.html Sat Feb 8 10:16:22 2014 UTC
@@ -0,0 +1,1 @@+<span><span ng-show="!canView">{{repository.code}}</span><a href="" ng-click="goView()" ng-show="canView">{{repository.code}}</a></span>
=======================================--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java Sat Feb 8 10:16:22 2014 UTC
@@ -1,5 +1,5 @@ /* - * Copyright 2013, Andrew Lindesay + * Copyright 2014, Andrew Lindesay * Distributed under the terms of the MIT License. */ =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sat Feb 8 10:16:22 2014 UTC
@@ -10,7 +10,6 @@ import com.google.common.base.Strings; import org.apache.cayenne.DataObject; import org.apache.cayenne.ObjectContext; -import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType; import org.haikuos.haikudepotserver.dataobjects.Pkg; import org.haikuos.haikudepotserver.dataobjects.Repository; import org.haikuos.haikudepotserver.dataobjects.User; @@ -26,20 +25,20 @@ @Service public class AuthorizationService {- private AuthorizationTargetType deriveTargetType(DataObject dataObject) {
+ private TargetType deriveTargetType(DataObject dataObject) { if(null==dataObject) return null; if(User.class.isAssignableFrom(dataObject.getClass())) { - return AuthorizationTargetType.USER; + return TargetType.USER; } if(Pkg.class.isAssignableFrom(dataObject.getClass())) { - return AuthorizationTargetType.PKG; + return TargetType.PKG; } if(Repository.class.isAssignableFrom(dataObject.getClass())) { - return AuthorizationTargetType.REPOSITORY; + return TargetType.REPOSITORY; }throw new IllegalStateException("the data object type '"+dataObject.getClass().getSimpleName()+"' is not handled");
=======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sat Feb 8 10:16:22 2014 UTC
@@ -5,30 +5,28 @@ package org.haikuos.haikudepotserver.security.model; -import org.haikuos.haikudepotserver.api1.model.AuthorizationTargetType; - public enum Permission { - REPOSITORY_VIEW(AuthorizationTargetType.REPOSITORY), - REPOSITORY_EDIT(AuthorizationTargetType.REPOSITORY), - REPOSITORY_IMPORT(AuthorizationTargetType.REPOSITORY), + REPOSITORY_VIEW(TargetType.REPOSITORY), + REPOSITORY_EDIT(TargetType.REPOSITORY), + REPOSITORY_IMPORT(TargetType.REPOSITORY), REPOSITORY_LIST(null), REPOSITORY_LIST_INACTIVE(null), - USER_VIEW(AuthorizationTargetType.USER), - USER_EDIT(AuthorizationTargetType.USER), - USER_CHANGEPASSWORD(AuthorizationTargetType.USER), + USER_VIEW(TargetType.USER), + USER_EDIT(TargetType.USER), + USER_CHANGEPASSWORD(TargetType.USER), USER_LIST(null), - PKG_EDITICON(AuthorizationTargetType.PKG); + PKG_EDITICON(TargetType.PKG); - private AuthorizationTargetType requiredTargetType; + private TargetType requiredTargetType; - Permission(AuthorizationTargetType requiredTargetType) { + Permission(TargetType requiredTargetType) { this.requiredTargetType = requiredTargetType; } - public AuthorizationTargetType getRequiredTargetType() { + public TargetType getRequiredTargetType() { return requiredTargetType; } =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js Sat Feb 8 10:16:22 2014 UTC
@@ -10,14 +10,31 @@ angular.module('haikudepotserver').directive('repositoryLabel',function() { return { restrict: 'E',- template:'<a href=\"\" ng-click=\"goView()\">{{repository.code}}</a>',
+ templateUrl:'/js/app/directive/repositorylabel.html', replace: true, scope: { repository: '=' }, controller: - ['$scope','$location', - function($scope,$location) { + ['$scope','$location','userState', + function($scope,$location,userState) { + + $scope.canView = false; ++ $scope.$watch('repository',function(newValue,oldValue) {
+ if(!newValue) { + $scope.canView = false; + } + else { + userState.areAuthorized([{ + targetType:'REPOSITORY', + targetIdentifier:newValue.code, + permissionCode:'REPOSITORY_VIEW' + }]).then(function(flag) { + $scope.canView = flag; + }); + } + }) $scope.goView = function() {$location.path('/viewrepository/'+$scope.repository.code).search({});
============================================================================== Revision: 11b560e3e392 Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sat Feb 8 11:31:22 2014 UTC Log: + better pagination action directives http://code.google.com/p/haiku-depot-web-app/source/detail?r=11b560e3e392 Added:/haikudepotserver-webapp/src/main/webapp/js/app/directive/paginationarrowdirective.js
Modified: /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html/haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html ======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/paginationarrowdirective.js Sat Feb 8 11:31:22 2014 UTC
@@ -0,0 +1,60 @@ +/* + * Copyright 2013, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +/**+ * <p>This directive will render the "page left" and "page right" arrows in a table of data.</p>
+ */ + +angular.module('haikudepotserver').directive('paginationArrow',function() { + return { + restrict: 'E', + link : function($scope,element,attributes) { + + var direction = attributes['direction']; + var onPageExpression = attributes['pageClick']; + var activeExpression = attributes['active']; + + var active = false; + var svgElement; + var svg; + + switch(direction) { + case 'right':+ svg = '<svg height=\"12\" width=\"12\"><path fill=\"black\" fill-opacity=\"0.5\" d=\"M0 4.5 L0 7.5 L4 7.5 L4 12 L12 6 L4 0 L4 4.5\"/></svg>';
+ break; + + case 'left':+ svg = '<svg height=\"12\" width=\"12\"><path fill=\"black\" fill-opacity=\"0.5\" d=\"M12 4.5 L12 7.5 L8 7.5 L8 12 L0 6 L8 0 L8 4.5\"/></svg>';
+ break; + + default:+ throw 'illegal direction on pagination arrow; '+direction;
+ } + + // replaces the element supplied with the SVG one. + element.after(svg); + svgElement = element.next(); + element.remove(); + + svgElement.on('click',function() { + if(active && onPageExpression) { + $scope.$apply(onPageExpression); + } + }) + + $scope.$watch(activeExpression,function(newValue,oldValue) { + if(newValue) { + svgElement.children().attr('fill-opacity','1.0'); + } + else { + svgElement.children().attr('fill-opacity','0.5'); + } + + active = newValue; + }); + + } + }; +}); =======================================--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sat Feb 8 11:31:22 2014 UTC
@@ -62,6 +62,7 @@<value>/js/app/directive/showifpkgpermissiondirective.js</value> <value>/js/app/directive/showifrepositorypermissiondirective.js</value> <value>/js/app/directive/showifpermissiondirective.js</value> + <value>/js/app/directive/paginationarrowdirective.js</value>
<value>/js/app/controller/viewpkgcontroller.js</value> <value>/js/app/controller/homecontroller.js</value>
=======================================--- /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Sat Feb 8 11:31:22 2014 UTC
@@ -232,8 +232,8 @@ } .table-general-pagination-container { - padding-top:2px; - padding-bottom:2px; + padding-top:3px; + padding-bottom:0px; padding-left:4px; padding-right:4px;position: absolute; /* assumes that this is inside a 'table-general-container' */
=======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html Sat Feb 8 11:31:22 2014 UTC
@@ -35,10 +35,8 @@ <div ng-show="pkgs && pkgs.length" class="table-general-container"> <div class="table-general-pagination-container">- <a href="" ng-click="goPreviousPage()" ng-show="0!=offset">«</a>
- <span ng-show="0==offset">«</span>- <a href="" ng-click="goNextPage()" ng-show="hasMore">»</a>
- <span ng-show="!hasMore">»</span>+ <pagination-arrow active="0!=offset" page-click="goPreviousPage()" direction="left"></pagination-arrow> + <pagination-arrow active="hasMore" page-click="goNextPage()" direction="right"></pagination-arrow>
</div> <table class="table-general"> =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html Sat Feb 8 11:31:22 2014 UTC
@@ -24,10 +24,8 @@<div ng-show="repositories && repositories.length" class="table-general-container">
<div class="table-general-pagination-container">- <a href="" ng-click="goPreviousPage()" ng-show="0!=offset">«</a>
- <span ng-show="0==offset">«</span>- <a href="" ng-click="goNextPage()" ng-show="hasMore">»</a>
- <span ng-show="!hasMore">»</span>+ <pagination-arrow active="0!=offset" page-click="goPreviousPage()" direction="left"></pagination-arrow> + <pagination-arrow active="hasMore" page-click="goNextPage()" direction="right"></pagination-arrow>
</div> <table class="table-general"> =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Sat Feb 8 11:31:22 2014 UTC
@@ -26,8 +26,8 @@ <div id="pkg-screenshot-container"> <div id="pkg-screenshot-pagination-container"> - <a href="">«</a> - <a href="">»</a>+ <pagination-arrow active="false" direction="left"></pagination-arrow> + <pagination-arrow active="false" direction="right"></pagination-arrow>
</div> <div id="pkg-screenshot-title"> ============================================================================== Revision: b001fd64677c Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sun Feb 9 06:49:48 2014 UTC Log: + create repository api + changes to update repository to support url changes http://code.google.com/p/haiku-depot-web-app/source/detail?r=b001fd64677c Added:/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/CreateRepositoryRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/CreateRepositoryResult.java
Modified:/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryRequest.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/RepositoryApiIT.java
======================================= --- /dev/null+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/CreateRepositoryRequest.java Sun Feb 9 06:49:48 2014 UTC
@@ -0,0 +1,14 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.api1.model.repository; + +public class CreateRepositoryRequest { + + public String code; + public String architectureCode; + public String url; + +} ======================================= --- /dev/null+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/CreateRepositoryResult.java Sun Feb 9 06:49:48 2014 UTC
@@ -0,0 +1,10 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.api1.model.repository; + +public class CreateRepositoryResult { + +} =======================================--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java Sat Feb 8 09:30:39 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java Sun Feb 9 06:49:48 2014 UTC
@@ -39,4 +39,12 @@TriggerImportRepositoryResult triggerImportRepository(TriggerImportRepositoryRequest triggerImportRepositoryRequest) throws ObjectNotFoundException;
+ /** + * <p>This method will create a repository. This method will throw+ * {@link org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException} if the architecture identified by a
+ * supplied code is not able to be found as an architecture.</p> + */ ++ CreateRepositoryResult createRepository(CreateRepositoryRequest createRepositoryRequest) throws ObjectNotFoundException;
+ } =======================================--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryRequest.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/UpdateRepositoryRequest.java Sun Feb 9 06:49:48 2014 UTC
@@ -10,13 +10,16 @@ public class UpdateRepositoryRequest { public enum Filter { - ACTIVE + ACTIVE, + URL }; public String code; public Boolean active; + public String url; + public List<Filter> filter; } =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sat Feb 8 09:30:39 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sun Feb 9 06:49:48 2014 UTC
@@ -16,6 +16,9 @@ import org.haikuos.haikudepotserver.api1.model.repository.*;import org.haikuos.haikudepotserver.api1.support.AuthorizationFailureException;
import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException; +import org.haikuos.haikudepotserver.api1.support.ValidationException; +import org.haikuos.haikudepotserver.api1.support.ValidationFailure; +import org.haikuos.haikudepotserver.dataobjects.Architecture; import org.haikuos.haikudepotserver.dataobjects.Repository; import org.haikuos.haikudepotserver.pkg.model.PkgRepositoryImportJob; import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification; @@ -214,6 +217,11 @@ break; + case URL:+ repositoryOptional.get().setUrl(updateRepositoryRequest.url); + logger.info("did set the url on repository {} to {}", updateRepositoryRequest.code,updateRepositoryRequest.url);
+ break; + default:throw new IllegalStateException("unhandled filter for updating a repository");
} @@ -228,5 +236,55 @@ return new UpdateRepositoryResult(); } + + @Override + public CreateRepositoryResult createRepository( + CreateRepositoryRequest createRepositoryRequest) + throws ObjectNotFoundException { + + Preconditions.checkNotNull(createRepositoryRequest); + + final ObjectContext context = serverRuntime.getContext(); + + authorizationService.check( + context, + tryObtainAuthenticatedUser(context).orNull(), + null, + Permission.REPOSITORY_CREATE); ++ Optional<Architecture> architectureOptional = Architecture.getByCode(context, createRepositoryRequest.architectureCode);
+ + if(!architectureOptional.isPresent()) {+ throw new ObjectNotFoundException(Architecture.class.getSimpleName(), createRepositoryRequest.architectureCode);
+ } + + // the code must be supplied. + + if(Strings.isNullOrEmpty(createRepositoryRequest.code)) {+ throw new ValidationException(new ValidationFailure(Repository.CODE_PROPERTY, "required"));
+ } ++ // check to see if there is an existing repository with the same code; non-unique.
+ + {+ Optional<Repository> repositoryOptional = Repository.getByCode(context, createRepositoryRequest.code);
+ + if(repositoryOptional.isPresent()) {+ throw new ValidationException(new ValidationFailure(Repository.CODE_PROPERTY, "unique"));
+ } + } + + Repository repository = context.newObject(Repository.class); + + repository.setCode(createRepositoryRequest.code); + repository.setActive(Boolean.TRUE); + repository.setUrl(createRepositoryRequest.url); + repository.setArchitecture(architectureOptional.get()); + + context.commitChanges(); + + return new CreateRepositoryResult(); + } + } =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sat Feb 8 10:16:22 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sun Feb 9 06:49:48 2014 UTC
@@ -111,6 +111,13 @@ Preconditions.checkNotNull(permission); Preconditions.checkNotNull(objectContext);Preconditions.checkState(deriveTargetType(target) == permission.getRequiredTargetType());
++ // if the authenticated user is not active then there should not be a situation arising where
+ // an authorization check is being made. + + if(null!=authenticatedUser && !authenticatedUser.getActive()) {+ throw new IllegalStateException("the authenticated user '"+authenticatedUser.getNickname()+"' is not active and so authorization queries cannot be resolved for them");
+ } switch(permission) { @@ -128,6 +135,9 @@ case REPOSITORY_LIST_INACTIVE:return null!=authenticatedUser && authenticatedUser.getIsRoot();
+ case REPOSITORY_CREATE:+ return null!=authenticatedUser && authenticatedUser.getIsRoot();
+ case USER_VIEW: case USER_EDIT: case USER_CHANGEPASSWORD: =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sat Feb 8 10:16:22 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sun Feb 9 06:49:48 2014 UTC
@@ -12,6 +12,7 @@ REPOSITORY_IMPORT(TargetType.REPOSITORY), REPOSITORY_LIST(null), REPOSITORY_LIST_INACTIVE(null), + REPOSITORY_CREATE(null), USER_VIEW(TargetType.USER), USER_EDIT(TargetType.USER), =======================================--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/RepositoryApiIT.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/RepositoryApiIT.java Sun Feb 9 06:49:48 2014 UTC
@@ -6,12 +6,14 @@ package org.haikuos.haikudepotsever.api1; import com.google.common.base.Optional; +import junit.framework.Assert; import org.apache.cayenne.ObjectContext; import org.fest.assertions.Assertions; import org.haikuos.haikudepotserver.api1.RepositoryApi; import org.haikuos.haikudepotserver.api1.model.pkg.SearchPkgsRequest; import org.haikuos.haikudepotserver.api1.model.repository.*; import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException; +import org.haikuos.haikudepotserver.api1.support.ValidationException; import org.haikuos.haikudepotserver.dataobjects.Repository; import org.haikuos.haikudepotsever.api1.support.AbstractIntegrationTest;import org.haikuos.haikudepotsever.api1.support.IntegrationTestSupportService;
@@ -83,5 +85,50 @@ Assertions.assertThat(result.architectureCode).isEqualTo("x86"); Assertions.assertThat(result.url).isEqualTo("file:///"); } + + @Test + public void testCreateRepository_ok() throws Exception { + setAuthenticatedUserToRoot(); + + CreateRepositoryRequest request = new CreateRepositoryRequest(); + request.architectureCode = "x86"; + request.code = "integrationtest"; + request.url = "http://www.somewhere.co.nz";; + + // ------------------------------------ + repositoryApi.createRepository(request); + // ------------------------------------ + + ObjectContext context = serverRuntime.getContext();+ Optional<Repository> repositoryAfterOptional = Repository.getByCode(context,"integrationtest"); + Assertions.assertThat(repositoryAfterOptional.get().getActive()).isTrue(); + Assertions.assertThat(repositoryAfterOptional.get().getArchitecture().getCode()).isEqualTo("x86"); + Assertions.assertThat(repositoryAfterOptional.get().getCode()).isEqualTo("integrationtest"); + Assertions.assertThat(repositoryAfterOptional.get().getUrl()).isEqualTo("http://www.somewhere.co.nz";);
+ } + + @Test + public void testCreateRepository_codeNotUnique() throws Exception {+ IntegrationTestSupportService.StandardTestData data = integrationTestSupportService.createStandardTestData();
+ setAuthenticatedUserToRoot(); + + CreateRepositoryRequest request = new CreateRepositoryRequest(); + request.architectureCode = "x86"; + request.code = data.repository.getCode(); + request.url = "http://www.somewhere.co.nz";; + + try { + // ------------------------------------ + repositoryApi.createRepository(request); + // ------------------------------------ ++ Assert.fail("the repository should not have been able to be created against an already existing repository code");
+ } + catch(ValidationException ve) {+ Assertions.assertThat(ve.getValidationFailures().size()).isEqualTo(1); + Assertions.assertThat(ve.getValidationFailures().get(0).getMessage()).isEqualTo("unique"); + Assertions.assertThat(ve.getValidationFailures().get(0).getProperty()).isEqualTo(Repository.CODE_PROPERTY);
+ } + } } ============================================================================== Revision: 04264ff1b771 Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sun Feb 9 10:00:28 2014 UTC Log: + add and edit repository functionality http://code.google.com/p/haiku-depot-web-app/source/detail?r=04264ff1b771 Added:/haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepository.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepositorycontroller.js
Modified:/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ErrorResolverImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Pkg.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersion.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersionUrl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Repository.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/User.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/support/AbstractDataObject.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java
/haikudepotserver-webapp/src/main/resources/messages.properties /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml/haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js
/haikudepotserver-webapp/src/main/webapp/js/app/routes.js/haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js
======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepository.html Sun Feb 9 10:00:28 2014 UTC
@@ -0,0 +1,63 @@ +<breadcrumbs items="breadcrumbItems"></breadcrumbs> + +<div class="content-container"> + <form name="addEditRepositoryForm" novalidate="novalidate"> + + <label for="code">Code</label>+ <div class="form-control-group" ng-class="deriveFormControlsContainerClasses('code')">
+ <div ng-show="amEditing" class="form-control-group-static"> + <code>{{workingRepository.code}}</code> + </div> + <input + id="code" + type="text" + name="code" + ng-show="!amEditing" + ng-change="codeChanged()" + ng-required="true" + ng-pattern="/^[a-z0-9]{2,16}$/" + ng-model="workingRepository.code"></input>+ <error-messages key-prefix="addEditRepository.code" error="addEditRepositoryForm.code.$error"></error-messages>
+ </div> + + <label for="code">Architecture</label> + <div class="form-control-group"> + <div ng-show="amEditing" class="form-control-group-static"> + <code>{{workingRepository.architecture.code}}</code> + </div> + <select + ng-show="!amEditing" + ng-model="workingRepository.architecture"+ ng-options="anArchitecture.code for anArchitecture in architectures">
+ </select> + </div> + + <label for="url">URL</label>+ <div class="form-control-group" ng-class="deriveFormControlsContainerClasses('url')">
+ <input + id="url" + type="url" + size="64" + name="url" + ng-required="true" + ng-model="workingRepository.url"></input>+ <error-messages key-prefix="addEditRepository.url" error="addEditRepositoryForm.url.$error"></error-messages>
+ </div> + + <div class="form-action-container"> + <button + ng-disabled="addEditRepositoryForm.$invalid" + ng-click="goSave()" + type="submit" + class="main-action"> + <span ng-show="amEditing">Save Changes</span> + <span ng-show="!amEditing">Add</span> + </button> + </div> + + </form> +</div> + +<div class="footer"></div> +<spinner spin="shouldSpin()"></spinner> + ======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepositorycontroller.js Sun Feb 9 10:00:28 2014 UTC
@@ -0,0 +1,163 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +angular.module('haikudepotserver').controller( + 'AddEditRepositoryController', + [ + '$scope','$log','$location','$routeParams', + 'jsonRpc','constants','breadcrumbs','userState','errorHandling','architectures', + function( + $scope,$log,$location,$routeParams,+ jsonRpc,constants,breadcrumbs,userState,errorHandling,architectures) {
+ + $scope.breadcrumbItems = undefined; + $scope.workingRepository = undefined; + $scope.architectures = architectures; + $scope.amEditing = !!$routeParams.code; + var amSaving = false; + + $scope.shouldSpin = function() { + return undefined == $scope.workingRepository || amSaving; + } + + $scope.deriveFormControlsContainerClasses = function(name) {+ return $scope.addEditRepositoryForm[name].$invalid ? ['form-control-group-error'] : [];
+ } ++ // the validity of this field may have been set to false because of the + // attempt to save it on the server-side. This function will be hit when
+ // the code changes so that validation does not apply. + + $scope.codeChanged = function() {+ $scope.addEditRepositoryForm.code.$setValidity('unique',true);
+ } + + refreshRepository(); + + function refreshBreadcrumbItems() { + var b = [ + breadcrumbs.createMore(), + breadcrumbs.createListRepositories() + ]; + + if($scope.amEditing) {+ b.push(breadcrumbs.createEditRepository($scope.workingRepository));
+ } + else { + b.push(breadcrumbs.createAddRepository()); + } + + $scope.breadcrumbItems = b; + } + + function refreshRepository() { + if($routeParams.code) { + jsonRpc.call( + constants.ENDPOINT_API_V1_REPOSITORY, + "getRepository", + [{ code : $routeParams.code }] + ).then( + function(result) { + $scope.workingRepository = { + code : result.code, + url : result.url,+ architecture : _.find(architectures, function(a) {
+ return a.code == result.architectureCode; + }) + } + refreshBreadcrumbItems(); + $log.info('fetched repository; '+result.code); + }, + function(err) { + errorHandling.handleJsonRpcError(err); + } + ); + } + else { + $scope.workingRepository = { + architecture : architectures[0] + }; + refreshBreadcrumbItems(); + } + }; + + $scope.goSave = function() { + + if($scope.addEditRepositoryForm.$invalid) {+ throw 'expected the save of a repository to only to be possible if the form is valid';
+ } + + amSaving = true; + + if($scope.amEditing) { + jsonRpc.call( + constants.ENDPOINT_API_V1_REPOSITORY, + "updateRepository", + [{ + filter : [ 'URL' ], + url : $scope.workingRepository.url, + code : $scope.workingRepository.code + }] + ).then( + function(result) {+ $log.info('did update repository; '+$scope.workingRepository.code); + $location.path('/viewrepository/'+$scope.workingRepository.code).search({});
+ }, + function(err) { + + switch(err.code) { + case jsonRpc.errorCodes.VALIDATION: + errorHandling.handleValidationFailures( + err.data.validationfailures, + $scope.addEditRepositoryForm); + break; + + default: + errorHandling.handleJsonRpcError(err); + break; + } + + amSaving = false; + } + ); + } + else { + jsonRpc.call( + constants.ENDPOINT_API_V1_REPOSITORY, + "createRepository", + [{+ architectureCode : $scope.workingRepository.architecture.code,
+ url : $scope.workingRepository.url, + code : $scope.workingRepository.code + }] + ).then( + function(result) {+ $log.info('did create repository; '+$scope.workingRepository.code); + $location.path('/viewrepository/'+$scope.workingRepository.code).search({});
+ }, + function(err) { + + switch(err.code) { + case jsonRpc.errorCodes.VALIDATION: + errorHandling.handleValidationFailures( + err.data.validationfailures, + $scope.addEditRepositoryForm); + break; + + default: + errorHandling.handleJsonRpcError(err); + break; + } + + amSaving = false; + } + ); + } + + } + + } + ] +); =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sun Feb 9 06:49:48 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sun Feb 9 10:00:28 2014 UTC
@@ -32,7 +32,6 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; -import javax.naming.ldap.PagedResultsControl; import java.util.List; @Component @@ -246,11 +245,13 @@ final ObjectContext context = serverRuntime.getContext(); - authorizationService.check( + if(!authorizationService.check( context, tryObtainAuthenticatedUser(context).orNull(), null, - Permission.REPOSITORY_CREATE); + Permission.REPOSITORY_ADD)) { + throw new AuthorizationFailureException(); + }Optional<Architecture> architectureOptional = Architecture.getByCode(context, createRepositoryRequest.architectureCode);
=======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ErrorResolverImpl.java Wed Nov 20 10:28:55 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ErrorResolverImpl.java Sun Feb 9 10:00:28 2014 UTC
@@ -1,5 +1,5 @@ /* - * Copyright 2013, Andrew Lindesay + * Copyright 2013-2014, Andrew Lindesay * Distributed under the terms of the MIT License. */ @@ -104,9 +104,10 @@public Map<String,String> apply(org.apache.cayenne.validation.ValidationFailure input) { if(BeanValidationFailure.class.isAssignableFrom(input.getClass())) { BeanValidationFailure beanValidationFailure = (BeanValidationFailure) input; + Object err = beanValidationFailure.getError();
return ImmutableMap.of("property", beanValidationFailure.getProperty(), - "message", beanValidationFailure.toString()); + "message", null!=err ? err.toString() : "");
}if(SimpleValidationFailure.class.isAssignableFrom(input.getClass())) {
=======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Pkg.java Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Pkg.java Sun Feb 9 10:00:28 2014 UTC
@@ -11,7 +11,7 @@ import org.apache.cayenne.ObjectContext; import org.apache.cayenne.exp.ExpressionFactory; import org.apache.cayenne.query.SelectQuery; -import org.apache.cayenne.validation.SimpleValidationFailure; +import org.apache.cayenne.validation.BeanValidationFailure; import org.apache.cayenne.validation.ValidationResult; import org.haikuos.haikudepotserver.dataobjects.auto._Pkg;import org.haikuos.haikudepotserver.dataobjects.support.CreateAndModifyTimestamped;
@@ -38,7 +38,7 @@ if(null != getName()) { if(!NAME_PATTERN.matcher(getName()).matches()) {- validationResult.addFailure(new SimpleValidationFailure(this,NAME_PATTERN + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,NAME_PROPERTY,"malformed"));
} } =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersion.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersion.java Sun Feb 9 10:00:28 2014 UTC
@@ -1,5 +1,5 @@ /* - * Copyright 2013, Andrew Lindesay + * Copyright 2013-2014, Andrew Lindesay * Distributed under the terms of the MIT License. */ @@ -15,7 +15,7 @@ import org.apache.cayenne.query.Ordering; import org.apache.cayenne.query.SelectQuery; import org.apache.cayenne.query.SortOrder; -import org.apache.cayenne.validation.SimpleValidationFailure; +import org.apache.cayenne.validation.BeanValidationFailure; import org.apache.cayenne.validation.ValidationResult; import org.haikuos.haikudepotserver.dataobjects.auto._PkgVersion;import org.haikuos.haikudepotserver.dataobjects.support.CreateAndModifyTimestamped;
@@ -78,31 +78,31 @@ if(null != getMajor()) { if(!MAJOR_PATTERN.matcher(getMajor()).matches()) {- validationResult.addFailure(new SimpleValidationFailure(this,MAJOR_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,MAJOR_PROPERTY,"malformed"));
} } if(null != getMinor()) { if(!MINOR_PATTERN.matcher(getMinor()).matches()) {- validationResult.addFailure(new SimpleValidationFailure(this,MINOR_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,MINOR_PROPERTY,"malformed"));
} } if(null != getMicro()) { if(!MICRO_PATTERN.matcher(getMicro()).matches()) {- validationResult.addFailure(new SimpleValidationFailure(this,MICRO_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,MICRO_PROPERTY,"malformed"));
} } if(null != getPreRelease()) { if(!PRE_RELEASE_PATTERN.matcher(getPreRelease()).matches()) {- validationResult.addFailure(new SimpleValidationFailure(this,PRE_RELEASE_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,PRE_RELEASE_PROPERTY,"malformed"));
} } if(null != getRevision()) { if(getRevision().intValue() <= 0) {- validationResult.addFailure(new SimpleValidationFailure(this,REVISION_PROPERTY + ".lessThanEqualZero")); + validationResult.addFailure(new BeanValidationFailure(this,REVISION_PROPERTY,"lessThanEqualZero"));
} } =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersionUrl.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersionUrl.java Sun Feb 9 10:00:28 2014 UTC
@@ -1,11 +1,11 @@ /* - * Copyright 2013, Andrew Lindesay + * Copyright 2013-2014, Andrew Lindesay * Distributed under the terms of the MIT License. */ package org.haikuos.haikudepotserver.dataobjects; -import org.apache.cayenne.validation.SimpleValidationFailure; +import org.apache.cayenne.validation.BeanValidationFailure; import org.apache.cayenne.validation.ValidationResult; import org.haikuos.haikudepotserver.dataobjects.auto._PkgVersionUrl; @@ -23,7 +23,7 @@ new URL(getUrl()); } catch(MalformedURLException mue) {- validationResult.addFailure(new SimpleValidationFailure(this,URL_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,URL_PROPERTY,"malformed"));
} } =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Repository.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Repository.java Sun Feb 9 10:00:28 2014 UTC
@@ -1,5 +1,5 @@ /* - * Copyright 2013, Andrew Lindesay + * Copyright 2013-2014, Andrew Lindesay * Distributed under the terms of the MIT License. */ @@ -12,7 +12,7 @@ import org.apache.cayenne.exp.ExpressionFactory; import org.apache.cayenne.query.ObjectIdQuery; import org.apache.cayenne.query.SelectQuery; -import org.apache.cayenne.validation.SimpleValidationFailure; +import org.apache.cayenne.validation.BeanValidationFailure; import org.apache.cayenne.validation.ValidationResult; import org.haikuos.haikudepotserver.dataobjects.auto._Repository; import org.haikuos.haikudepotserver.dataobjects.support.Coded; @@ -40,7 +40,7 @@ if(null != getCode()) { if(!CODE_PATTERN.matcher(getCode()).matches()) {- validationResult.addFailure(new SimpleValidationFailure(this,CODE_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,CODE_PROPERTY,"malformed"));
} } =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/User.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/User.java Sun Feb 9 10:00:28 2014 UTC
@@ -1,5 +1,5 @@ /* - * Copyright 2013, Andrew Lindesay + * Copyright 2013-2014, Andrew Lindesay * Distributed under the terms of the MIT License. */ @@ -11,7 +11,7 @@ import org.apache.cayenne.ObjectContext; import org.apache.cayenne.exp.ExpressionFactory; import org.apache.cayenne.query.SelectQuery; -import org.apache.cayenne.validation.SimpleValidationFailure; +import org.apache.cayenne.validation.BeanValidationFailure; import org.apache.cayenne.validation.ValidationResult; import org.haikuos.haikudepotserver.dataobjects.auto._User; @@ -65,19 +65,19 @@ if(null != getNickname()) { if(!NICKNAME_PATTERN.matcher(getNickname()).matches()) {- validationResult.addFailure(new SimpleValidationFailure(this,NICKNAME_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,NICKNAME_PROPERTY,"malformed"));
} } if(null != getPasswordHash()) {if(!PASSWORDHASH_PATTERN.matcher(getPasswordHash()).matches()) { - validationResult.addFailure(new SimpleValidationFailure(this,PASSWORD_HASH_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,PASSWORD_HASH_PROPERTY,"malformed"));
} } if(null != getPasswordSalt()) {if(!PASSWORDSALT_PATTERN.matcher(getPasswordSalt()).matches()) { - validationResult.addFailure(new SimpleValidationFailure(this,PASSWORD_HASH_PROPERTY + ".malformed")); + validationResult.addFailure(new BeanValidationFailure(this,PASSWORD_HASH_PROPERTY,"malformed"));
} } =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/support/AbstractDataObject.java Sat Jan 18 09:59:17 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/support/AbstractDataObject.java Sun Feb 9 10:00:28 2014 UTC
@@ -1,12 +1,12 @@ /* - * Copyright 2013, Andrew Lindesay + * Copyright 2013-2014, Andrew Lindesay * Distributed under the terms of the MIT License. */ package org.haikuos.haikudepotserver.dataobjects.support; import org.apache.cayenne.CayenneDataObject; -import org.apache.cayenne.validation.SimpleValidationFailure; +import org.apache.cayenne.validation.BeanValidationFailure; import org.apache.cayenne.validation.ValidationResult; import java.util.regex.Pattern; @@ -33,7 +33,7 @@ if(null != coded.getCode()) { if(!CODE_PATTERN.matcher(coded.getCode()).matches()) {- validationResult.addFailure(new SimpleValidationFailure(this,"code.malformed")); + validationResult.addFailure(new BeanValidationFailure(this,"code","malformed"));
} } } =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sun Feb 9 06:49:48 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sun Feb 9 10:00:28 2014 UTC
@@ -135,7 +135,7 @@ case REPOSITORY_LIST_INACTIVE:return null!=authenticatedUser && authenticatedUser.getIsRoot();
- case REPOSITORY_CREATE: + case REPOSITORY_ADD:return null!=authenticatedUser && authenticatedUser.getIsRoot();
case USER_VIEW: =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sun Feb 9 06:49:48 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sun Feb 9 10:00:28 2014 UTC
@@ -12,7 +12,7 @@ REPOSITORY_IMPORT(TargetType.REPOSITORY), REPOSITORY_LIST(null), REPOSITORY_LIST_INACTIVE(null), - REPOSITORY_CREATE(null), + REPOSITORY_ADD(null), USER_VIEW(TargetType.USER), USER_EDIT(TargetType.USER), =======================================--- /haikudepotserver-webapp/src/main/resources/messages.properties Sat Jan 18 09:59:17 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages.properties Sun Feb 9 10:00:28 2014 UTC
@@ -39,6 +39,12 @@ home.viewCriteriaType.mostrecent=Most Recently Updated home.viewCriteriaType.mostviewed=Most Viewed +addEditRepository.code.required=The code is required.+addEditRepository.code.pattern=The code must consist of between 2 and 16 lower case letters and digits. +addEditRepository.code.unique=The code nominated for this repository is already in-use; nominate a different code.
+addEditRepository.url.required=The url to the .hpkr data must be supplied. +addEditRepository.url.url=The url must be well-formed. + # Test case test.it=Test line for integration testing =======================================--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sat Feb 8 11:31:22 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sun Feb 9 10:00:28 2014 UTC
@@ -76,6 +76,7 @@<value>/js/app/controller/runtimeinformationcontroller.js</value> <value>/js/app/controller/listrepositoriescontroller.js</value> <value>/js/app/controller/viewrepositorycontroller.js</value> + <value>/js/app/controller/addeditrepositorycontroller.js</value>
<value>/js/app/service/jsonrpcservice.js</value> <value>/js/app/service/messagesourceservice.js</value>
=======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html Sat Feb 8 11:31:22 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html Sun Feb 9 10:00:28 2014 UTC
@@ -50,6 +50,9 @@<li ng-show="!amShowingInactive" show-if-permission="'REPOSITORY_LIST_INACTIVE'"> <a href="" ng-click="goShowInactive()">Show inactive repositories</a>
</li> + <li show-if-permission="'REPOSITORY_ADD'"> + <a href="" ng-click="goAdd()">Add repository</a> + </li> </ul> </div> =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js Sun Feb 9 10:00:28 2014 UTC
@@ -106,6 +106,9 @@ return $scope.hasMore ? [] : ['disabled']; } + $scope.goAdd = function() { + $location.path('/addrepository').search({}); + } } ] =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html Sat Feb 8 09:30:39 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html Sun Feb 9 10:00:28 2014 UTC
@@ -32,6 +32,9 @@<li ng-show="repository.active" repository="repository" show-if-repository-permission="'REPOSITORY_IMPORT'">
<a href="" ng-click="goTriggerImport()">Trigger import</a> </li>+ <li repository="repository" show-if-repository-permission="'REPOSITORY_EDIT'">
+ <a href="" ng-click="goEdit()">Edit</a> + </li> </ul> </div> =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js Sat Feb 8 09:30:39 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js Sun Feb 9 10:00:28 2014 UTC
@@ -62,6 +62,10 @@ $scope.goDeactivate = function() { updateActive(false); } + + $scope.goEdit = function() {+ $location.path('/editrepository/' + $scope.repository.code);
+ } /*** <p>This function will initiate an import of a repository. These run sequentially so it may not happen
=======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Sun Feb 9 10:00:28 2014 UTC
@@ -8,6 +8,28 @@ '$routeProvider', function($routeProvider) { $routeProvider + .when('/addrepository',{ + controller:'AddEditRepositoryController',+ templateUrl:'/js/app/controller/addeditrepository.html',
+ resolve: { + 'architectures':[ + 'referenceData', function(referenceData) { + return referenceData.architectures(); + } + ] + } + }) + .when('/editrepository/:code',{ + controller:'AddEditRepositoryController',+ templateUrl:'/js/app/controller/addeditrepository.html',
+ resolve: { + 'architectures':[ + 'referenceData', function(referenceData) { + return referenceData.architectures(); + } + ] + } + }).when('/viewrepository/:code',{controller:'ViewRepositoryController', templateUrl:'/js/app/controller/viewrepository.html'}) .when('/listrepositories',{controller:'ListRepositoriesController', templateUrl:'/js/app/controller/listrepositories.html'}) .when('/runtimeinformation',{controller:'RuntimeInformationController', templateUrl:'/js/app/controller/runtimeinformation.html'})
=======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Sun Feb 9 10:00:28 2014 UTC
@@ -13,6 +13,20 @@ var BreadcrumbsService = { + createEditRepository : function(repository) { + return { + title : 'Edit ' + repository.code, + path : '/editrepository/' + repository.code + } + }, + + createAddRepository : function(repository) { + return { + title : 'Add Repository', + path : '/addrepository' + } + }, + createViewRepository : function(repository) { return { title : repository.code, =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Sun Feb 9 10:00:28 2014 UTC
@@ -43,6 +43,33 @@ handleUnknownLocation : function() { $log.error('unknown location; ' + $location.path()); $location.path("/error").search({}); + }, + + /**+ * <p>Splay validation failures into the form. The validation failures are objects that are + * returned as part of the error envelope in RPC invocations. This method will return true + * if all of the validation errors were able to be assigned to models in the form.</p>
+ */ ++ handleValidationFailures : function(validationFailures, form) {
+ var result = true; + + if(validationFailures) { + _.each(validationFailures, function(vf) { + if(vf.property && vf.property.length) { + var model = form[vf.property]; + + if(model) { + model.$setValidity(vf.message, false); + } + else { + result = false; + } + } + }) + } + + return result; } }; ============================================================================== Revision: 68df28d58697 Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Sun Feb 9 10:38:52 2014 UTC Log: + documentation update + changes around add / edit repository http://code.google.com/p/haiku-depot-web-app/source/detail?r=68df28d58697 Modified: /haikudepotserver-docs/src/main/latex/docs/part-api.tex /haikudepotserver-docs/src/main/latex/docs/part-deployment.tex/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Repository.java
/haikudepotserver-webapp/src/main/resources/messages.properties/haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepository.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepositorycontroller.js
=======================================--- /haikudepotserver-docs/src/main/latex/docs/part-api.tex Sun Feb 2 09:38:27 2014 UTC +++ /haikudepotserver-docs/src/main/latex/docs/part-api.tex Sun Feb 9 10:38:52 2014 UTC
@@ -129,6 +129,7 @@ \end{itemize} \subsubsection{Import Repository Data} +\label{api-importrepositorydata}This API provides a mechanism by which an external client is able to trigger the application to start importing package-related data from a remote repository. This API is provided as REST because the client is likely to be scripted using a scripting language and REST is the most appropriate protocol to employ in this situation. This invocation will trigger the import process, but the import process will execute in a background thread in the application server and will not block the client.
=======================================--- /haikudepotserver-docs/src/main/latex/docs/part-deployment.tex Wed Jan 29 10:34:56 2014 UTC +++ /haikudepotserver-docs/src/main/latex/docs/part-deployment.tex Sun Feb 9 10:38:52 2014 UTC
@@ -44,27 +44,7 @@ \subsection{Setting Up Repositories} \label{settinguprepositories} -{\it This section is temporary until a user-interface exists for this.} --The application server will pull ``.hpkr'' files from remote repositories that contain information about the packages at that repository. At the time of writing, it is necessary to configure the repositories by hand. A repository can be added using a SQL shell.
--In this ficticious example, an ``.hpkr'' file has been placed in the temporary directory. The following SQL command can be executed to add a repository that pulls the ``.hpkr'' from the temporary directory. In reality, the real repositories will have an internet-accessible URL.
- -\begin{verbatim} -INSERT INTO - haikudepot.repository ( - id, active, create_timestamp, modify_timestamp, - architecture_id, code, url) - VALUES ( - nextval('haikudepot.repository_seq'), true, now(), now(), - (SELECT id FROM haikudepot.architecture WHERE code='x86'), - 'test', - 'file:///tmp/repo.hpkr'); -\end{verbatim} --In order to prompt the system to import this ".hpkr" file and populate some repository data into the system, you can use the curl tool as follows;
- -\framebox{\tt curl "http://localhost:8080/importrepositorydata?code=test"}+The application server will pull ``.hpkr'' files from remote repositories that contain information about the packages at that repository. Authenticated as root, it is possible to use the ``more'' link at the top of the home page to get to the repositories, to add a repository and to trigger the import of a repository. There also exists an HTTP GET invocation (see \ref{api-importrepositorydata}) that can be made to trigger the import of a repository from another system.
\subsection{Accessing the Web Environment} =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Repository.java Sun Feb 9 10:00:28 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/Repository.java Sun Feb 9 10:38:52 2014 UTC
@@ -18,6 +18,8 @@ import org.haikuos.haikudepotserver.dataobjects.support.Coded;import org.haikuos.haikudepotserver.dataobjects.support.CreateAndModifyTimestamped;
+import java.net.MalformedURLException; +import java.net.URL; import java.util.List;public class Repository extends _Repository implements CreateAndModifyTimestamped, Coded {
@@ -43,6 +45,15 @@validationResult.addFailure(new BeanValidationFailure(this,CODE_PROPERTY,"malformed"));
} } + + if(null != getUrl()) { + try { + new URL(getUrl()); + } + catch(MalformedURLException mue) {+ validationResult.addFailure(new BeanValidationFailure(this,URL_PROPERTY,"malformed"));
+ } + } } =======================================--- /haikudepotserver-webapp/src/main/resources/messages.properties Sun Feb 9 10:00:28 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages.properties Sun Feb 9 10:38:52 2014 UTC
@@ -43,7 +43,7 @@addEditRepository.code.pattern=The code must consist of between 2 and 16 lower case letters and digits. addEditRepository.code.unique=The code nominated for this repository is already in-use; nominate a different code.
addEditRepository.url.required=The url to the .hpkr data must be supplied. -addEditRepository.url.url=The url must be well-formed. +addEditRepository.url.malformed=The url must be well-formed. # Test case =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepository.html Sun Feb 9 10:00:28 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepository.html Sun Feb 9 10:38:52 2014 UTC
@@ -36,9 +36,10 @@<div class="form-control-group" ng-class="deriveFormControlsContainerClasses('url')">
<input id="url" - type="url" + type="text" size="64" name="url" + ng-change="urlChanged()" ng-required="true" ng-model="workingRepository.url"></input><error-messages key-prefix="addEditRepository.url" error="addEditRepositoryForm.url.$error"></error-messages>
=======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepositorycontroller.js Sun Feb 9 10:00:28 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/addeditrepositorycontroller.js Sun Feb 9 10:38:52 2014 UTC
@@ -33,6 +33,10 @@ $scope.codeChanged = function() {$scope.addEditRepositoryForm.code.$setValidity('unique',true);
} + + $scope.urlChanged = function() {+ $scope.addEditRepositoryForm.url.$setValidity('malformed',true);
+ } refreshRepository();