[haiku-depot-web] [haiku-depot-web-app] 5 new revisions pushed by haiku.li...@xxxxxxxxx on 2014-06-28 10:53 GMT

  • From: haiku-depot-web-app@xxxxxxxxxxxxxx
  • To: haiku-depot-web@xxxxxxxxxxxxx
  • Date: Sat, 28 Jun 2014 10:53:37 +0000

master moved from 25e802ef3a9d to 18e4a2cea192

5 new revisions:

Revision: d965ca8807f5
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Fri Jun 20 10:48:29 2014 UTC
Log: + small API change to make the pkg search be consistent with the API f...
http://code.google.com/p/haiku-depot-web-app/source/detail?r=d965ca8807f5

Revision: ec3626ad559f
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jun 21 11:24:37 2014 UTC
Log:      + ability to list all of the versions for a package
http://code.google.com/p/haiku-depot-web-app/source/detail?r=ec3626ad559f

Revision: 567bf1de584e
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Mon Jun 23 10:01:05 2014 UTC
Log:      + ability to list / search users...
http://code.google.com/p/haiku-depot-web-app/source/detail?r=567bf1de584e

Revision: b13dbbc35447
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jun 28 10:23:44 2014 UTC
Log:      + implement token bearer authentication
http://code.google.com/p/haiku-depot-web-app/source/detail?r=b13dbbc35447

Revision: 18e4a2cea192
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jun 28 10:46:28 2014 UTC
Log:      + resolve small problem with logout function
http://code.google.com/p/haiku-depot-web-app/source/detail?r=18e4a2cea192

==============================================================================
Revision: d965ca8807f5
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Fri Jun 20 10:48:29 2014 UTC
Log: + small API change to make the pkg search be consistent with the API for get pkg

http://code.google.com/p/haiku-depot-web-app/source/detail?r=d965ca8807f5

Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js

=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsResult.java Wed Jun 4 11:36:29 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsResult.java Fri Jun 20 10:48:29 2014 UTC
@@ -7,12 +7,20 @@

 import org.haikuos.haikudepotserver.api1.support.AbstractSearchResult;

+import java.util.List;
+
public class SearchPkgsResult extends AbstractSearchResult<SearchPkgsResult.Pkg> {

     public static class Pkg {
         public String name;
         public Long modifyTimestamp;
-        public SearchPkgsResult.PkgVersion version;
+
+        /**
+ * <p>This versions value should only contain the one item actually, but is
+         * provided in this form to retain consistency with other API.</p>
+         */
+
+        public List<PkgVersion> versions;
         public Float derivedRating;
     }

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Wed Jun 4 11:36:29 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Fri Jun 20 10:48:29 2014 UTC
@@ -238,7 +238,7 @@
                         resultVersion.viewCounter = input.getViewCounter();
resultVersion.architectureCode = input.getArchitecture().getCode();

-                        resultPkg.version = resultVersion;
+ resultPkg.versions = Collections.singletonList(resultVersion);

                         return resultPkg;
                     }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html Thu Jun 19 09:19:46 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html Fri Jun 20 10:48:29 2014 UTC
@@ -82,9 +82,9 @@
<rating-indicator rating="{{pkg.derivedRating}}"></rating-indicator>
                         </span>
                     </td>
- <td><version-label version="pkg.version"></version-label></td> - <td ng-show="'MOSTRECENT'==selectedViewCriteriaTypeOption.code"><span class="muted">{{pkg.version.createTimestamp|timestamp}}</span></td> - <td ng-show="'MOSTVIEWED'==selectedViewCriteriaTypeOption.code"><span class="muted">{{pkg.version.viewCounter}}</span></td> + <td><version-label version="pkg.version[0]"></version-label></td> + <td ng-show="'MOSTRECENT'==selectedViewCriteriaTypeOption.code"><span class="muted">{{pkg.version[0].createTimestamp|timestamp}}</span></td> + <td ng-show="'MOSTVIEWED'==selectedViewCriteriaTypeOption.code"><span class="muted">{{pkg.version[0].viewCounter}}</span></td>
                 </tr>
                 </tbody>
             </table>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js Thu Jun 19 09:19:46 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js Fri Jun 20 10:48:29 2014 UTC
@@ -271,10 +271,7 @@
             $scope.goViewPkg = function(pkg) {

                 breadcrumbs.pushAndNavigate(
-                    breadcrumbs.createViewPkgWithSpecificVersionFromPkg({
-                        name: pkg.name,
-                        versions: [ pkg.version ]
-                    })
+ breadcrumbs.createViewPkgWithSpecificVersionFromPkg(pkg)
                 );

                 return false;

==============================================================================
Revision: ec3626ad559f
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jun 21 11:24:37 2014 UTC
Log:      + ability to list all of the versions for a package

http://code.google.com/p/haiku-depot-web-app/source/detail?r=ec3626ad559f

Added:
/haikudepotserver-webapp/src/main/webapp/js/app/controller/listpkgversionsforpkg.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/listpkgversionsforpkgcontroller.js
Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/PkgVersionType.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java
 /haikudepotserver-webapp/src/main/resources/messages.properties
 /haikudepotserver-webapp/src/main/resources/messages_de.properties
 /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml
 /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/routes.js
/haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js

=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listpkgversionsforpkg.html Sat Jun 21 11:24:37 2014 UTC
@@ -0,0 +1,27 @@
+<breadcrumbs items="breadcrumbItems"></breadcrumbs>
+
+<div class="content-container">
+
+    <h1>
+        <span ng-show="pkg.versions.length > 1">
+ <message key="listPkgVersionsForPkg.title.plural" parameters="[pkg.versions.length,pkg.name]"></message>
+        </span>
+        <span ng-show="1==pkg.versions.length">
+ <message key="listPkgVersionsForPkg.title" parameters="[pkg.versions.length,pkg.name]"></message>
+        </span>
+    </h1>
+
+    <ul>
+        <li ng-repeat="pkgVersion in pkg.versions">
+ <pkg-version-label pkg-version="pkgVersion"></pkg-version-label>
+            <span class="muted" ng-show="pkgVersion.isLatest">
+                (<message key="listPkgVersionsForPkg.latest"></message>)
+            </span>
+        </li>
+    </ul>
+
+</div>
+
+<div class="footer"></div>
+<spinner spin="shouldSpin()"></spinner>
+
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listpkgversionsforpkgcontroller.js Sat Jun 21 11:24:37 2014 UTC
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013-2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+angular.module('haikudepotserver').controller(
+    'ListPkgVersionsForPkgController',
+    [
+        '$scope','$log','$routeParams',
+        'jsonRpc','constants','errorHandling',
+        'breadcrumbs','pkg','userState',
+        function(
+            $scope,$log,$routeParams,
+            jsonRpc,constants,errorHandling,
+            breadcrumbs,pkg,userState) {
+
+            $scope.pkg = undefined;
+
+            $scope.shouldSpin = function() {
+                return undefined == $scope.pkg;
+            };
+
+ // When the breadcrumbs are re-created, assume that we must have come through the main version first.
+
+            function refreshBreadcrumbItems() {
+                breadcrumbs.mergeCompleteStack([
+                    breadcrumbs.createHome(),
+ breadcrumbs.createViewPkgWithSpecificVersionFromPkg($scope.pkg),
+                    breadcrumbs.createListPkgVersionsForPkg($scope.pkg)
+                ]);
+            }
+
+            function refetchPkg() {
+
+                jsonRpc.call(
+                    constants.ENDPOINT_API_V1_PKG,
+                    'getPkg',
+                    [{
+                        name : $routeParams.name,
+                        versionType : 'ALL',
+                        incrementViewCounter : false,
+ naturalLanguageCode: userState.naturalLanguageCode(),
+                    }]
+                ).then(
+                    function(result) {
+ $log.info('fetched '+result.name+' pkg with ' + result.versions.length + ' versions');
+                        $scope.pkg = result;
+
+ // add a pkg.name onto each version so it is in the right structure for the pkg-version-label
+                        // directive.
+
+ $scope.pkg.versions = _.map($scope.pkg.versions,function(v) { + return _.extend(v,{ pkg : { name : $scope.pkg.name }});
+                        });
+
+                        refreshBreadcrumbItems();
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError();
+                    }
+                );
+            }
+
+            refetchPkg();
+
+        }
+    ]
+);
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/PkgVersionType.java Mon Apr 21 10:48:33 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/PkgVersionType.java Sat Jun 21 11:24:37 2014 UTC
@@ -11,6 +11,7 @@
  */

 public enum PkgVersionType {
+    ALL,
     LATEST,
     NONE,
     SPECIFIC
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Fri Jun 20 10:48:29 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Sat Jun 21 11:24:37 2014 UTC
@@ -30,6 +30,7 @@
 import org.haikuos.haikudepotserver.security.AuthorizationService;
 import org.haikuos.haikudepotserver.security.model.Permission;
 import org.haikuos.haikudepotserver.support.VersionCoordinates;
+import org.haikuos.haikudepotserver.support.VersionCoordinatesComparator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Value;
@@ -39,6 +40,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
@@ -371,7 +373,7 @@
architectureOptional = Architecture.getByCode(context, request.architectureCode);
         }

- NaturalLanguage naturalLanguage = getNaturalLanguage(context, request.naturalLanguageCode); + final NaturalLanguage naturalLanguage = getNaturalLanguage(context, request.naturalLanguageCode);

         GetPkgResult result = new GetPkgResult();
         Pkg pkg = getPkg(context, request.name);
@@ -389,6 +391,55 @@

         switch(request.versionType) {

+ // might be used to show a history of the versions. If an architecture is present then it will + // only return versions for that architecture. If no architecure is present then it will return
+            // versions for all architectures.
+
+            case ALL: {
+
+ List<PkgVersion> allVersions = PkgVersion.getForPkg(context, pkg);
+
+                if(architectureOptional.isPresent()) {
+                    final Architecture a = architectureOptional.get();
+
+                    allVersions = Lists.newArrayList(Iterables.filter(
+                            allVersions,
+                            new Predicate<PkgVersion>() {
+                                @Override
+ public boolean apply(PkgVersion pkgVersion) { + return pkgVersion.getArchitecture().equals(a);
+                                }
+                            }
+                    ));
+                }
+
+                // now sort those.
+
+ final VersionCoordinatesComparator vcc = new VersionCoordinatesComparator();
+
+ Collections.sort(allVersions, new Comparator<PkgVersion>() {
+                    @Override
+                    public int compare(PkgVersion o1, PkgVersion o2) {
+ int result = vcc.compare(o1.toVersionCoordinates(),o2.toVersionCoordinates());
+
+                        if(0==result) {
+ result = o1.getArchitecture().getCode().compareTo(o2.getArchitecture().getCode());
+                        }
+
+                        return result;
+                    }
+                });
+
+ result.versions = Lists.transform(allVersions, new Function<PkgVersion, GetPkgResult.PkgVersion>() {
+                    @Override
+ public GetPkgResult.PkgVersion apply(PkgVersion pkgVersion) { + return createGetPkgResultPkgVersion(pkgVersion,naturalLanguage);
+                    }
+                });
+
+            }
+            break;
+
             case SPECIFIC: {
                 if (!architectureOptional.isPresent()) {
throw new IllegalStateException("the specified architecture was not able to be found; " + request.architectureCode);
=======================================
--- /haikudepotserver-webapp/src/main/resources/messages.properties Thu Jun 19 10:17:07 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages.properties Sat Jun 21 11:24:37 2014 UTC
@@ -34,6 +34,7 @@
 breadcrumb.addUserRating.title=Add User Rating
 breadcrumb.editUserRating.title=Edit User Rating
 breadcrumb.viewUserRating.title=View User Rating
+breadcrumb.listPkgVersionsForPkg.title=All Versions

 about.title=About
 about.mainDescription=This is an application-server (version {0}) called \
@@ -174,6 +175,7 @@
 viewPkg.categories.title=Categories
 viewPkg.categories.none=None
 viewPkg.versionViews.title=Version Views
+viewPkg.listPkgVersionsAction.title=All versions
 viewPkg.visitWebSiteAction.title=Visit web site
 viewPkg.removeIconAction.title=Remove icon
 viewPkg.editIconAction.title=Edit icon
@@ -207,6 +209,10 @@
 viewUser.editAction.title=Edit
 viewUser.logoutAction.title=Logout

+listPkgVersionsForPkg.title={0} Version for {1}
+listPkgVersionsForPkg.title.plural={0} Versions for {1}
+listPkgVersionsForPkg.latest=Latest
+
 banner.action.more=About Haiku Depot Server
 banner.action.authenticate=User login
 banner.action.createUser=Register new user
=======================================
--- /haikudepotserver-webapp/src/main/resources/messages_de.properties Thu Jun 19 10:17:07 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages_de.properties Sat Jun 21 11:24:37 2014 UTC
@@ -32,6 +32,7 @@
 breadcrumb.addUserRating.title=Bewertung hinzufügen
 breadcrumb.editUserRating.title=Bewertung bearbeiten
 breadcrumb.viewUserRating.title=Bewertungen
+breadcrumb.listPkgVersionsForPkg.title=Alle Versionen

 about.title=Über
about.mainDescription=Dies ist der Anwendungs-Server (Version {0}) "Haiku Depot Server", \
@@ -175,6 +176,7 @@
 viewPkg.editIconAction.title=Icon bearbeiten
 viewPkg.downloadIconHvifAction.title=Icon im 'hvif' Format herunterladen
 viewPkg.editScreenshotsAction.title=Screenshots bearbeiten
+viewPkg.visitWebSiteAction.title=Alle Versionen
 viewPkg.editPkgCategoriesAction.title=Kategorien bearbeiten
 viewPkg.editVersionLocalizationAction.title=Übersetzungen bearbeiten
 viewPkg.userRating.title=Bewertung
@@ -203,6 +205,8 @@
 viewUser.editAction.title=Bearbeiten
 viewUser.logoutAction.title=Abmelden

+listPkgVersionsForPkg.latest=Neueste
+
 banner.action.more=Über Haiku Depot Server
 banner.action.authenticate=Benutzer anmelden
 banner.action.createUser=Neuen Benutzer registrieren
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sun Jun 15 09:24:04 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sat Jun 21 11:24:37 2014 UTC
@@ -90,6 +90,7 @@
<value>/js/app/controller/addedituserratingcontroller.js</value> <value>/js/app/controller/paginationcontrolplaygroundcontroller.js</value> <value>/js/app/controller/viewuserratingcontroller.js</value> + <value>/js/app/controller/listpkgversionsforpkgcontroller.js</value>

<value>/js/app/service/jsonrpcservice.js</value> <value>/js/app/service/messagesourceservice.js</value>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Wed Jun 4 11:36:29 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Sat Jun 21 11:24:37 2014 UTC
@@ -153,6 +153,12 @@
     <div id="pkg-actions-container">

         <ul>
+            <li>
+                <a href="" ng-click="goListPkgVersions()">
+ <message key="viewPkg.listPkgVersionsAction.title"></message>
+                </a>
+            </li>
+
             <li>
<a target="_blank" ng-show="homePageLink()" href="{{homePageLink()}}"> <message key="viewPkg.visitWebSiteAction.title"></message>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Thu Jun 19 09:19:46 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Sat Jun 21 11:24:37 2014 UTC
@@ -278,6 +278,10 @@
             // ---------------------
             // ACTIONS FOR PACKAGE

+            $scope.goListPkgVersions = function() {
+ breadcrumbs.pushAndNavigate(breadcrumbs.createListPkgVersionsForPkg($scope.pkg));
+            }
+
// this is used to cause an authentication in relation to adding a user rating
             $scope.goAuthenticate = function() {
breadcrumbs.pushAndNavigate(breadcrumbs.createAuthenticate());
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Thu Jun 19 09:19:46 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Sat Jun 21 11:24:37 2014 UTC
@@ -25,6 +25,7 @@
.when('/user/:nickname/changepassword',{controller:'ChangePasswordController', templateUrl:'/js/app/controller/changepassword.html'}) .when('/userrating/:code/edit',{controller:'AddEditUserRatingController', templateUrl:'/js/app/controller/addedituserrating.html'}) .when('/userrating/:code',{controller:'ViewUserRatingController', templateUrl:'/js/app/controller/viewuserrating.html'}) + .when('/pkg/:name/listpkgversions',{controller:'ListPkgVersionsForPkgController', templateUrl:'/js/app/controller/listpkgversionsforpkg.html'}) .when(pkgVersionPrefix,{controller:'ViewPkgController', templateUrl:'/js/app/controller/viewpkg.html'}) .when(pkgVersionPrefix+'/editicon',{controller:'EditPkgIconController', templateUrl:'/js/app/controller/editpkgicon.html'}) .when(pkgVersionPrefix+'/editscreenshots',{controller:'EditPkgScreenshotsController', templateUrl:'/js/app/controller/editpkgscreenshots.html'})
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Thu Jun 19 09:19:46 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Sat Jun 21 11:24:37 2014 UTC
@@ -198,7 +198,7 @@

                 if(peek().search && $location.path() == peek().path) {
                     _.each(peek().search, function(value,key) {
-                       $location.search(key,value);
+                        $location.search(key,value);
                     });
                 }

@@ -266,18 +266,39 @@
path : generateBaseUrlForPkg(pkgName,versionCoordinates,architectureCode)
                 });
             }
+
+            /**
+ * <p>From a list of pkg versions, tries to find the one that is identified as the + * latest. If this is not possible then it will take the first one.</p>
+             */
+
+            function latestVersion(pkgVersions) {
+                if(!pkgVersions || !pkgVersions.length) {
+ throw 'a package version is required to get the latest';
+                }
+
+ var pkgVersion = _.findWhere(pkgVersions, { isLatest : true } );
+
+                if(!pkgVersion) {
+                    pkgVersion = pkgVersions[0];
+                }
+
+                return pkgVersion;
+            }

function createManipulatePkgBreadcrumbItem(pkgWithVersion0, pathSuffix, titlePortion) { if(!pkgWithVersion0 || !pkgWithVersion0.versions | | !pkgWithVersion0.versions.length) { throw 'a package version is required to form a breadcrumb';
                 }
+
+                var pkgVersion = latestVersion(pkgWithVersion0.versions);

                 return applyDefaults({
                     titleKey : 'breadcrumb.'+titlePortion+'.title',
                     path : generateBaseUrlForPkg(
                         pkgWithVersion0.name,
-                        pkgWithVersion0.versions[0],
- pkgWithVersion0.versions[0].architectureCode) + '/' + pathSuffix
+                        pkgVersion,
+                        pkgVersion.architectureCode) + '/' + pathSuffix
                 });
             }

@@ -490,6 +511,13 @@
                     });
                 },

+                createListPkgVersionsForPkg : function(pkg) {
+                    return applyDefaults({
+ titleKey : 'breadcrumb.listPkgVersionsForPkg.title',
+                        path : '/pkg/'+pkg.name+'/listpkgversions'
+                    });
+                },
+
                 createEditPkgCategories : function(pkg) {
return createManipulatePkgBreadcrumbItem(pkg, 'editcategories', 'editPkgCategories');
                 },
@@ -579,11 +607,13 @@
                     if(!pkg || !pkg.versions.length) {
throw 'a package with a package version are required to form a breadcrumb';
                     }
+
+                    var pkgVersion = latestVersion(pkg.versions);

                     return createViewPkgBreadcrumbItem(
                         pkg.name,
-                        pkg.versions[0],
-                        pkg.versions[0].architectureCode);
+                        pkgVersion,
+                        pkgVersion.architectureCode);
                 },

createViewPkgWithSpecificVersionFromRouteParams : function(routeParams) {

==============================================================================
Revision: 567bf1de584e
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Mon Jun 23 10:01:05 2014 UTC
Log:      + ability to list / search users
+ ability to view users
+ ability to active / deactivate users
+ fix issues with pkg rating derivations not happening on update

http://code.google.com/p/haiku-depot-web-app/source/detail?r=567bf1de584e

Added:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/SearchUsersRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/SearchUsersResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/UserOrchestrationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/model/UserSearchSpecification.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/userrating/LocalUserRatingDerviationService.java
 /haikudepotserver-webapp/src/main/webapp/css/listusers.css
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/listusers.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/listuserscontroller.js /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/userrating/NoopUserRatingDerviationService.java
Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/UpdateUserRequest.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgOrchestrationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/cayenne/UserRatingDerivationTriggerListener.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/userrating/UserRatingDerivationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/userrating/UserRatingOrchestrationService.java
 /haikudepotserver-webapp/src/main/resources/messages.properties
 /haikudepotserver-webapp/src/main/resources/messages_de.properties
 /haikudepotserver-webapp/src/main/resources/spring/application-context.xml
 /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml
/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/controller/viewuser.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/viewusercontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewuserrating.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewuserratingcontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/directive/banner.html
/haikudepotserver-webapp/src/main/webapp/js/app/directive/bannerdirective.js /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/main/webapp/js/app/directive/showifuserpermissiondirective.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifuserratingpermissiondirective.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/userlabeldirective.js
 /haikudepotserver-webapp/src/main/webapp/js/app/routes.js
/haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/api1/UserApiIT.java
 /haikudepotserver-webapp/src/test/resources/spring/test.xml

=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/SearchUsersRequest.java Mon Jun 23 10:01:05 2014 UTC
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+import org.haikuos.haikudepotserver.api1.support.AbstractSearchRequest;
+
+public class SearchUsersRequest extends AbstractSearchRequest {
+
+    public Boolean includeInactive;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/SearchUsersResult.java Mon Jun 23 10:01:05 2014 UTC
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+import org.haikuos.haikudepotserver.api1.support.AbstractSearchResult;
+
+public class SearchUsersResult extends AbstractSearchResult<SearchUsersResult.User> {
+
+    public static class User {
+
+        public String nickname;
+        public Boolean active;
+
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/UserOrchestrationService.java Mon Jun 23 10:01:05 2014 UTC
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.user;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.query.EJBQLQuery;
+import org.haikuos.haikudepotserver.dataobjects.User;
+import org.haikuos.haikudepotserver.support.cayenne.LikeHelper;
+import org.haikuos.haikudepotserver.user.model.UserSearchSpecification;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * <p>This service undertakes non-trivial operations on users.</p>
+ */
+
+@Service
+public class UserOrchestrationService {
+
+ protected static Logger logger = LoggerFactory.getLogger(UserOrchestrationService.class);
+
+    // ------------------------------
+    // SEARCH
+
+    private String prepareWhereClause(
+            List<Object> parameterAccumulator,
+            ObjectContext context,
+            UserSearchSpecification search) {
+
+        Preconditions.checkNotNull(parameterAccumulator);
+        Preconditions.checkNotNull(search);
+        Preconditions.checkNotNull(context);
+        Preconditions.checkState(search.getOffset() >= 0);
+        Preconditions.checkState(search.getLimit() > 0);
+
+        List<String> parts = Lists.newArrayList();
+
+        if(!Strings.isNullOrEmpty(search.getExpression())) {
+            switch(search.getExpressionType()) {
+
+                case CONTAINS:
+ parts.add("LOWER(u.nickname) LIKE ?" + (parameterAccumulator.size() + 1)); + parameterAccumulator.add("%" + LikeHelper.ESCAPER.escape(search.getExpression()) + "%");
+                    break;
+
+                default:
+ throw new IllegalStateException("unknown expression type " + search.getExpressionType().name());
+
+            }
+        }
+
+        if(!search.getIncludeInactive()) {
+            parts.add("u.active = ?" + (parameterAccumulator.size() + 1));
+            parameterAccumulator.add(Boolean.TRUE);
+        }
+
+        return Joiner.on(" AND ").join(parts);
+    }
+
+    // WILL WORK AFTER CAY-1932 IS IMPLEMENTED!
+//    private SelectQuery prepare(
+//            ObjectContext context,
+//            UserSearchSpecification searchSpecification) {
+//        Preconditions.checkNotNull(context);
+//        Preconditions.checkNotNull(searchSpecification);
+//        List<Expression> expressions = Lists.newArrayList();
+//
+//        if(!Strings.isNullOrEmpty(searchSpecification.getExpression())) {
+//            switch(searchSpecification.getExpressionType()) {
+//
+//                case CONTAINS:
+//                    expressions.add(ExpressionFactory.likeExp(
+//                            User.NICKNAME_PROPERTY,
+// "%" + LikeHelper.ESCAPER.escape(searchSpecification.getExpression()) + "%"
+//                    ));
+//                    break;
+//
+//                default:
+// throw new IllegalStateException("unknown expression type " + searchSpecification.getExpressionType().name());
+//
+//            }
+//        }
+//
+//        if(!searchSpecification.getIncludeInactive()) {
+//            expressions.add(ExpressionFactory.matchExp(
+//                    User.ACTIVE_PROPERTY,
+//                    Boolean.TRUE));
+//        }
+//
+//        return new SelectQuery(
+//                User.class,
+//                ExpressionHelper.andAll(expressions),
+//                Collections.singletonList(new Ordering(
+//                        User.NICKNAME_PROPERTY,
+//                        SortOrder.ASCENDING)));
+//    }
+
+    /**
+     * <p>Undertakes a search for users.</p>
+     */
+
+    public List<User> search(
+            ObjectContext context,
+            UserSearchSpecification searchSpecification) {
+        Preconditions.checkNotNull(context);
+        Preconditions.checkNotNull(searchSpecification);
+
+ StringBuilder queryBuilder = new StringBuilder("SELECT u FROM User AS u");
+        List<Object> parameters = Lists.newArrayList();
+ String where = prepareWhereClause(parameters, context, searchSpecification);
+
+        if(!Strings.isNullOrEmpty(where)) {
+            queryBuilder.append(" WHERE ");
+            queryBuilder.append(where);
+        }
+
+        queryBuilder.append(" ORDER BY u.nickname ASC");
+
+        EJBQLQuery query = new EJBQLQuery(queryBuilder.toString());
+
+        for (int i = 0; i < parameters.size(); i++) {
+            query.setParameter(i+1, parameters.get(i));
+        }
+
+        query.setFetchOffset(searchSpecification.getOffset());
+        query.setFetchLimit(searchSpecification.getLimit());
+
+        return context.performQuery(query);
+    }
+
+    /**
+     * <p>Find out the total number of results that would be yielded from
+     * a search if the search were not constrained.</p>
+     */
+
+    public long total(
+            ObjectContext context,
+            UserSearchSpecification searchSpecification) {
+        Preconditions.checkNotNull(context);
+        Preconditions.checkNotNull(searchSpecification);
+
+ StringBuilder queryBuilder = new StringBuilder("SELECT COUNT(u) FROM User u");
+        List<Object> parameters = Lists.newArrayList();
+ String where = prepareWhereClause(parameters, context, searchSpecification);
+
+        if(!Strings.isNullOrEmpty(where)) {
+            queryBuilder.append(" WHERE ");
+            queryBuilder.append(where);
+        }
+
+        EJBQLQuery query = new EJBQLQuery(queryBuilder.toString());
+
+        for (int i = 0; i < parameters.size(); i++) {
+            query.setParameter(i+1, parameters.get(i));
+        }
+
+        List<Long> result = (List<Long>) context.performQuery(query);
+
+        switch(result.size()) {
+
+            case 1:
+                return result.get(0).longValue();
+
+            default:
+ throw new IllegalStateException("the result should have contained a single long result");
+
+        }
+    }
+
+    // ------------------------------
+
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/model/UserSearchSpecification.java Mon Jun 23 10:01:05 2014 UTC
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.user.model;
+
+import org.haikuos.haikudepotserver.support.AbstractSearchSpecification;
+
+public class UserSearchSpecification extends AbstractSearchSpecification {
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/userrating/LocalUserRatingDerviationService.java Mon Jun 23 10:01:05 2014 UTC
@@ -0,0 +1,67 @@
+package org.haikuos.haikudepotserver.userrating;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.haikudepotserver.support.AbstractLocalBackgroundProcessingService; +import org.haikuos.haikudepotserver.userrating.model.UserRatingDerivationJob;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Resource;
+
+/**
+ * <p>This implementation of the {@link org.haikuos.haikudepotserver.userrating.UserRatingDerivationService} + * operates in the same runtime as the application and has no persistence or distributed behaviour.</p>
+ */
+
+public class LocalUserRatingDerviationService
+        extends AbstractLocalBackgroundProcessingService
+        implements UserRatingDerivationService {
+
+ protected static Logger logger = LoggerFactory.getLogger(UserRatingDerivationService.class);
+
+    @Resource
+    UserRatingOrchestrationService userRatingOrchestrationService;
+
+    public void submit(final UserRatingDerivationJob job) {
+        Preconditions.checkNotNull(job);
+ Preconditions.checkState(null!=executor, "the service is not running, but a job is being submitted");
+        executor.submit(new UserRatingDerivationJobRunnable(this, job));
+ logger.info("have submitted job to derive user rating; {}", job.toString());
+    }
+
+    protected void run(UserRatingDerivationJob job) {
+        Preconditions.checkNotNull(job);
+ userRatingOrchestrationService.updateUserRatingDerivation(job.getPkgName());
+    }
+
+    /**
+ * <p>This is the object that gets enqueued to actually do the work.</p>
+     */
+
+ public static class UserRatingDerivationJobRunnable implements Runnable {
+
+        private UserRatingDerivationJob job;
+
+        private LocalUserRatingDerviationService service;
+
+        public UserRatingDerivationJobRunnable(
+                LocalUserRatingDerviationService service,
+                UserRatingDerivationJob job) {
+            Preconditions.checkNotNull(service);
+            Preconditions.checkNotNull(job);
+            this.service = service;
+            this.job = job;
+        }
+
+        public UserRatingDerivationJob getJob() {
+            return job;
+        }
+
+        @Override
+        public void run() {
+            service.run(job);
+        }
+
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/css/listusers.css Mon Jun 23 10:01:05 2014 UTC
@@ -0,0 +1,12 @@
+/*
+This specific layout works off the premise that there is only one table on this page.
+*/
+
+.list-users .table-general > tbody > tr > td:nth-child(1) {
+    width: 10%;
+    text-align: center;
+}
+
+.list-users .table-general > tbody > tr > td:nth-child(2) {
+    width:90%;
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listusers.html Mon Jun 23 10:01:05 2014 UTC
@@ -0,0 +1,64 @@
+<breadcrumbs items="breadcrumbItems"></breadcrumbs>
+
+<div class="content-container list-users">
+
+    <div id="search-criteria-container">
+        <div>
+            <input
+                    autocomplete="off"
+                    ng-model="searchExpression"
+                    return-key-press="goSearch()"
+                    placeholder="Filter by code..."></input>
+            <button ng-click="goSearch()">
+                <message key="listUsers.search.go.title"></message>
+            </button>
+        </div>
+    </div>
+
+    <!-- RESULTS -->
+
+    <div id="search-results-container">
+
+ <div ng-show="users.items && 0==users.items.length" class="info-container"> + <strong><message key="listUsers.noResults.title"></message>; </strong>
+            <message key="listUsers.noResults.description"></message>
+        </div>
+
+ <div ng-show="users.items && users.items.length" class="table-general-container">
+
+            <div class="table-general-pagination-container">
+                <pagination-control
+                        link-count="9"
+                        max="users.max"
+                        total="users.total"
+                        offset="users.offset"></pagination-control>
+            </div>
+
+            <table class="table-general">
+                <thead>
+ <th><message key="listUsers.table.active.title"></message></th> + <th><message key="listUsers.table.nickname.title"></message></th>
+                </thead>
+                <tbody>
+                <tr ng-repeat="user in users.items">
+ <td><active-indicator state="user.active"></active-indicator></td>
+                    <td><user-label user="user"></user-label></td>
+                </tr>
+                </tbody>
+            </table>
+
+        </div>
+    </div>
+
+    <ul>
+        <li ng-show="!amShowingInactive">
+            <a href="" ng-click="goShowInactive()">
+ <message key="listUsers.showInactiveAction.title"></message>
+            </a>
+        </li>
+    </ul>
+
+</div>
+
+<div class="footer"></div>
+<spinner spin="shouldSpin()"></spinner>
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listuserscontroller.js Mon Jun 23 10:01:05 2014 UTC
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+angular.module('haikudepotserver').controller(
+    'ListUsersController',
+    [
+        '$scope','$log','$location',
+        'jsonRpc','constants',
+        'breadcrumbs','errorHandling',
+        function(
+            $scope,$log,$location,
+            jsonRpc,constants,
+            breadcrumbs,errorHandling) {
+
+            breadcrumbs.mergeCompleteStack([
+                breadcrumbs.createHome(),
+ breadcrumbs.applyCurrentLocation(breadcrumbs.createListUsers())
+            ]);
+
+            var PAGESIZE = 15;
+
+            $scope.users = {
+                items: undefined,
+                offset: 0,
+                max: PAGESIZE,
+                total: undefined
+            };
+
+            function clearUsers() {
+                $scope.users.items = undefined;
+                $scope.users.total = undefined;
+                $scope.users.offset = 0;
+            }
+
+            $scope.amShowingInactive = false;
+            var amFetchingUsers = false;
+
+            refetchUsersAtFirstPage();
+
+            $scope.shouldSpin = function() {
+                return amFetchingUsers;
+            };
+
+            $scope.goShowInactive = function() {
+                $scope.amShowingInactive = true;
+                refetchUsersAtFirstPage();
+            };
+
+            // ---- LIST MANAGEMENT
+
+            $scope.goSearch = function() {
+                clearUsers();
+                refetchUsersAtFirstPage();
+            };
+
+            function refetchUsersAtFirstPage() {
+                $scope.users.offset = 0;
+                refetchUsers();
+            }
+
+            function refetchUsers() {
+
+                amFetchingUsers = true;
+
+                jsonRpc.call(
+                    constants.ENDPOINT_API_V1_USER,
+                    "searchUsers",
+                    [{
+                        expression : $scope.searchExpression,
+                        expressionType : 'CONTAINS',
+                        includeInactive : $scope.amShowingInactive,
+                        offset : $scope.users.offset,
+                        limit : $scope.users.max
+                    }]
+                ).then(
+                    function(result) {
+                        $scope.users.items = result.items;
+                        $scope.users.total = result.total;
+                        $log.info('found '+result.items.length+' users');
+                        amFetchingUsers = false;
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+
+            }
+
+            // ---- EVENTS
+
+            $scope.$watch('users.offset', function() {
+                refetchUsers();
+            });
+
+        }
+    ]
+);
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/userrating/NoopUserRatingDerviationService.java Mon Jun 23 10:01:05 2014 UTC
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.userrating;
+
+import org.haikuos.haikudepotserver.userrating.model.UserRatingDerivationJob;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NoopUserRatingDerviationService implements UserRatingDerivationService {
+
+ protected static Logger logger = LoggerFactory.getLogger(NoopUserRatingDerviationService.class);
+
+    @Override
+    public void submit(UserRatingDerivationJob job) {
+ logger.info("did submit request to derive user rating for pkg; {} -- will ignore (noop)",job.getPkgName());
+    }
+}
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java Fri Mar 28 10:40:14 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java Mon Jun 23 10:01:05 2014 UTC
@@ -56,4 +56,10 @@

ChangePasswordResult changePassword(ChangePasswordRequest changePasswordRequest) throws ObjectNotFoundException, AuthorizationFailureException, ValidationException;

+    /**
+     * <p>This method will allow a search for users.</p>
+     */
+
+    SearchUsersResult searchUsers(SearchUsersRequest searchUsersRequest);
+
 }
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/UpdateUserRequest.java Fri Mar 28 10:40:14 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/UpdateUserRequest.java Mon Jun 23 10:01:05 2014 UTC
@@ -10,13 +10,16 @@
 public class UpdateUserRequest {

     public enum Filter {
-        NATURALLANGUAGE
+        NATURALLANGUAGE,
+        ACTIVE
     };

     public String nickname;

     public String naturalLanguageCode;

+    public Boolean active;
+
     public List<Filter> filter;

 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Tue Apr 29 10:56:37 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Mon Jun 23 10:01:05 2014 UTC
@@ -5,9 +5,12 @@

 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 com.google.common.util.concurrent.Uninterruptibles;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.configuration.server.ServerRuntime;
@@ -16,14 +19,21 @@
 import org.haikuos.haikudepotserver.captcha.CaptchaService;
 import org.haikuos.haikudepotserver.dataobjects.NaturalLanguage;
 import org.haikuos.haikudepotserver.dataobjects.User;
+import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification;
 import org.haikuos.haikudepotserver.security.AuthenticationService;
 import org.haikuos.haikudepotserver.security.AuthorizationService;
 import org.haikuos.haikudepotserver.security.model.Permission;
+import org.haikuos.haikudepotserver.user.UserOrchestrationService;
+import org.haikuos.haikudepotserver.user.model.UserSearchSpecification;
+import org.haikuos.haikudepotserver.userrating.UserRatingDerivationService;
+import org.haikuos.haikudepotserver.userrating.UserRatingOrchestrationService; +import org.haikuos.haikudepotserver.userrating.model.UserRatingDerivationJob;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;

 import javax.annotation.Resource;
+import java.util.List;
 import java.util.concurrent.TimeUnit;

 @Component
@@ -43,6 +53,15 @@
     @Resource
     AuthenticationService authenticationService;

+    @Resource
+    UserOrchestrationService userOrchestrationService;
+
+    @Resource
+    UserRatingOrchestrationService userRatingOrchestrationService;
+
+    @Resource
+    UserRatingDerivationService userRatingDerivationService;
+
     @Override
public UpdateUserResult updateUser(UpdateUserRequest updateUserRequest) throws ObjectNotFoundException {

@@ -52,6 +71,7 @@

         final ObjectContext context = serverRuntime.getContext();
         User authUser = obtainAuthenticatedUser(context);
+        boolean activeDidChange = false;

Optional<User> user = User.getByNickname(context, updateUserRequest.nickname);

@@ -66,6 +86,7 @@
         for (UpdateUserRequest.Filter filter : updateUserRequest.filter) {

             switch (filter) {
+
                 case NATURALLANGUAGE:

if(Strings.isNullOrEmpty(updateUserRequest.naturalLanguageCode)) {
@@ -84,10 +105,21 @@

logger.info("will update the natural language on the user {} to {}", user.get().toString(), naturalLanguageOptional.get().toString());

+                    break;
+
+                case ACTIVE:
+                    if(null==updateUserRequest.active) {
+ throw new IllegalStateException("the 'active' attribute is required to configure active on the user.");
+                    }
+
+ activeDidChange = user.get().getActive() != updateUserRequest.active;
+                    user.get().setActive(updateUserRequest.active);
+
                     break;

                 default:
throw new IllegalStateException("unknown filter in edit user; " + filter.name());
+
             }

         }
@@ -95,6 +127,23 @@
         if(context.hasChanges()) {
             context.commitChanges();
             logger.info("did update the user {}", user.get().toString());
+
+ // if a user is made active or inactive will have some impact on the user-ratings.
+
+            if(activeDidChange) {
+ List<String> pkgNames = userRatingOrchestrationService.pkgNamesEffectedByUserActiveStateChange(
+                        context, user.get());
+
+                logger.info(
+ "will update user rating derivation for {} packages owing to active state change on user {}",
+                        pkgNames.size(),
+                        user.get().toString());
+
+                for(String pkgName : pkgNames) {
+ userRatingDerivationService.submit(new UserRatingDerivationJob(pkgName));
+                }
+            }
+
         }
         else {
logger.info("no changes in updating the user {}", user.get().toString());
@@ -301,5 +350,57 @@
         return new ChangePasswordResult();
     }

+    @Override
+ public SearchUsersResult searchUsers(SearchUsersRequest searchUsersRequest) {
+        Preconditions.checkNotNull(searchUsersRequest);
+
+        final ObjectContext context = serverRuntime.getContext();
+
+        if(!authorizationService.check(
+                context,
+                tryObtainAuthenticatedUser(context).orNull(),
+                null,
+                Permission.USER_LIST)) {
+            throw new AuthorizationFailureException();
+        }
+
+ UserSearchSpecification specification = new UserSearchSpecification();
+        String exp = searchUsersRequest.expression;
+
+        if(null!=exp) {
+            exp = Strings.emptyToNull(exp.trim().toLowerCase());
+        }
+
+        specification.setExpression(exp);
+
+        if(null!= searchUsersRequest.expressionType) {
+            specification.setExpressionType(
+ PkgSearchSpecification.ExpressionType.valueOf(searchUsersRequest.expressionType.name()));
+        }
+
+        specification.setLimit(searchUsersRequest.limit);
+        specification.setOffset(searchUsersRequest.offset);
+ specification.setIncludeInactive(null!= searchUsersRequest.includeInactive && searchUsersRequest.includeInactive);
+
+        SearchUsersResult result = new SearchUsersResult();
+ List<User> searchedUsers = userOrchestrationService.search(context,specification);
+
+ result.total = userOrchestrationService.total(context,specification);
+        result.items = Lists.newArrayList(Iterables.transform(
+                searchedUsers,
+                new Function<User, SearchUsersResult.User>() {
+                    @Override
+                    public SearchUsersResult.User apply(User user) {
+ SearchUsersResult.User resultUser = new SearchUsersResult.User();
+                        resultUser.active = user.getActive();
+                        resultUser.nickname = user.getNickname();
+                        return resultUser;
+                    }
+                }
+        ));
+
+        return result;
+
+    }

 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgOrchestrationService.java Sun May 11 12:08:29 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgOrchestrationService.java Mon Jun 23 10:01:05 2014 UTC
@@ -33,9 +33,7 @@
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;

-import javax.annotation.Resource;
 import javax.imageio.ImageIO;
-import javax.sql.DataSource;
 import java.awt.image.BufferedImage;
 import java.io.*;
 import java.util.Collections;
@@ -61,9 +59,6 @@

     private ImageHelper imageHelper = new ImageHelper();

-    @Resource
-    DataSource dataSource;
-
     // ------------------------------
     // HELP

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/cayenne/UserRatingDerivationTriggerListener.java Fri Jun 6 11:10:53 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/cayenne/UserRatingDerivationTriggerListener.java Mon Jun 23 10:01:05 2014 UTC
@@ -70,6 +70,7 @@

     @Override
     public void postUpdate(Object entity) {
+        triggerUpdateUserRatingDerivationForAssociatedPackage(entity);
     }

     @Override
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/userrating/UserRatingDerivationService.java Wed Jun 4 11:36:29 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/userrating/UserRatingDerivationService.java Mon Jun 23 10:01:05 2014 UTC
@@ -5,13 +5,7 @@

 package org.haikuos.haikudepotserver.userrating;

-import com.google.common.base.Preconditions;
-import org.haikuos.haikudepotserver.support.AbstractLocalBackgroundProcessingService; import org.haikuos.haikudepotserver.userrating.model.UserRatingDerivationJob;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.Resource;

 /**
* <p>User ratings are collected from users. User ratings can then be used to derive a overall or 'aggregated'
@@ -20,55 +14,8 @@
* This service also offers the function of making these derivations in the background.</p>
  */

-// TODO; at some time in the future, when it is worthwhile, use a JMS broker to not loose jobs.
+public interface UserRatingDerivationService {

-public class UserRatingDerivationService extends AbstractLocalBackgroundProcessingService {
-
- protected static Logger logger = LoggerFactory.getLogger(UserRatingDerivationService.class);
-
-    @Resource
-    UserRatingOrchestrationService userRatingOrchestrationService;
-
-    public void submit(final UserRatingDerivationJob job) {
-        Preconditions.checkNotNull(job);
- Preconditions.checkState(null!=executor, "the service is not running, but a job is being submitted");
-        executor.submit(new UserRatingDerivationJobRunnable(this, job));
- logger.info("have submitted job to derive user rating; {}", job.toString());
-    }
-
-    protected void run(UserRatingDerivationJob job) {
-        Preconditions.checkNotNull(job);
- userRatingOrchestrationService.updateUserRatingDerivation(job.getPkgName());
-    }
-
-    /**
- * <p>This is the object that gets enqueued to actually do the work.</p>
-     */
-
- public static class UserRatingDerivationJobRunnable implements Runnable {
-
-        private UserRatingDerivationJob job;
-
-        private UserRatingDerivationService service;
-
-        public UserRatingDerivationJobRunnable(
-                UserRatingDerivationService service,
-                UserRatingDerivationJob job) {
-            Preconditions.checkNotNull(service);
-            Preconditions.checkNotNull(job);
-            this.service = service;
-            this.job = job;
-        }
-
-        public UserRatingDerivationJob getJob() {
-            return job;
-        }
-
-        @Override
-        public void run() {
-            service.run(job);
-        }
-
-    }
+    public void submit(final UserRatingDerivationJob job);

 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/userrating/UserRatingOrchestrationService.java Wed Jun 4 11:36:29 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/userrating/UserRatingOrchestrationService.java Mon Jun 23 10:01:05 2014 UTC
@@ -12,6 +12,7 @@
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.query.EJBQLQuery;
+import org.apache.cayenne.query.SelectQuery;
 import org.haikuos.haikudepotserver.dataobjects.Pkg;
 import org.haikuos.haikudepotserver.dataobjects.PkgVersion;
 import org.haikuos.haikudepotserver.dataobjects.User;
@@ -69,6 +70,8 @@

         List<String> whereExpressions = Lists.newArrayList();

+        whereExpressions.add("ur.user.active = true");
+
         if (!search.getIncludeInactive()) {
whereExpressions.add("ur." + UserRating.ACTIVE_PROPERTY + " = true");
         }
@@ -229,6 +232,21 @@

     // ------------------------------
     // DERIVATION ALGORITHM
+
+    /**
+ * <p>If a user has their active / inactive state swapped, it is possible that it may have some bearing + * on user ratings because user rating values from inactive users are not included. The impact may be + * small, but may also be significant in cases where the package has few ratings anyway. This method + * will return a list of package names for those packages that may be accordingly effected by a change
+     * in a user's active flag.</p>
+     */
+
+ public List<String> pkgNamesEffectedByUserActiveStateChange(ObjectContext context, User user) {
+        Preconditions.checkNotNull(user);
+ EJBQLQuery query = new EJBQLQuery("SELECT DISTINCT ur.pkgVersion.pkg.name FROM UserRating ur WHERE ur.user=?1");
+        query.setParameter(1,user);
+        return (List<String>) context.performQuery(query);
+    }

     private float averageAsFloat(Collection<Short> ratings) {
         Preconditions.checkNotNull(ratings);
@@ -303,7 +321,11 @@
logger.info("unable to establish a user rating for {}", pkgOptional.get());
         }
         else {
- logger.info("user rating established for {}; {}", pkgOptional.get(), rating.get());
+            logger.info(
+                    "user rating established for {}; {} (sample {})",
+                    pkgOptional.get(),
+                    rating.get().getRating(),
+                    rating.get().getSampleSize());
         }

         if(rating.isPresent()) {
=======================================
--- /haikudepotserver-webapp/src/main/resources/messages.properties Sat Jun 21 11:24:37 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages.properties Mon Jun 23 10:01:05 2014 UTC
@@ -35,6 +35,7 @@
 breadcrumb.editUserRating.title=Edit User Rating
 breadcrumb.viewUserRating.title=View User Rating
 breadcrumb.listPkgVersionsForPkg.title=All Versions
+breadcrumb.listUsers.title=List Users

 about.title=About
 about.mainDescription=This is an application-server (version {0}) called \
@@ -172,6 +173,13 @@
 listRepositories.showInactiveAction.title=Show inactive repositories
 listRepositories.addAction.title=Add repository

+listUsers.search.go.title=Go
+listUsers.noResults.title=No Results
+listUsers.noResults.description=There were no users able to be found from your search criteria.
+listUsers.table.active.title=Active
+listUsers.table.nickname.title=Nickname
+listUsers.showInactiveAction.title=Show inactive users
+
 viewPkg.categories.title=Categories
 viewPkg.categories.none=None
 viewPkg.versionViews.title=Version Views
@@ -198,8 +206,8 @@
viewRepository.importTriggered.description=the application will import the repository's data soon.
 viewRepository.active.title=Active
 viewRepository.url.title=URL
-viewRepository.deactiveAction.title=Deactivate
-viewRepository.reactiveAction.title=Reactivate
+viewRepository.deactivateAction.title=Deactivate
+viewRepository.reactivateAction.title=Reactivate
 viewRepository.triggerImportAction.title=Trigger import
 viewRepository.editAction.title=Edit

@@ -208,6 +216,8 @@
 viewUser.changePaswordAction.title=Change password
 viewUser.editAction.title=Edit
 viewUser.logoutAction.title=Logout
+viewUser.deactivateAction.title=Deactivate
+viewUser.reactivateAction.title=Reactivate

 listPkgVersionsForPkg.title={0} Version for {1}
 listPkgVersionsForPkg.title.plural={0} Versions for {1}
@@ -219,7 +229,8 @@
 banner.action.user=View details for {0}
 banner.action.logout=Logout {0}
 banner.action.naturalLanguagePrefix=Language:
-banner.action.repositories=Package Repositories
+banner.action.repositories=List Package Repositories
+banner.action.users=List Users

 naturalLanguage.en=English
 naturalLanguage.de=Deutsch
=======================================
--- /haikudepotserver-webapp/src/main/resources/messages_de.properties Sat Jun 21 11:24:37 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages_de.properties Mon Jun 23 10:01:05 2014 UTC
@@ -33,6 +33,7 @@
 breadcrumb.editUserRating.title=Bewertung bearbeiten
 breadcrumb.viewUserRating.title=Bewertungen
 breadcrumb.listPkgVersionsForPkg.title=Alle Versionen
+breadcrumb.listUsers.title=Benutzer anzeigen

 about.title=Über
about.mainDescription=Dies ist der Anwendungs-Server (Version {0}) "Haiku Depot Server", \
@@ -168,6 +169,13 @@
 listRepositories.showInactiveAction.title=Zeige inaktive Depots
 listRepositories.addAction.title=Depot hinzufügen

+listRepositories.search.go.title=Los
+listRepositories.noResults.title=Keine Treffer
+listRepositories.noResults.description=Mit den verwendeten Suchkriterien wurden keine Benutzer gefunden.
+listRepositories.table.active.title=Aktiv
+listRepositories.table.nickname.title=Benutzername
+listRepositories.showInactiveAction.title=Zeige inaktive Benutzer
+
 viewPkg.categories.title=Kategorien
 viewPkg.categories.none=keine
 viewPkg.versionViews.title=Versionsanzeigen
@@ -194,8 +202,8 @@
viewRepository.importTriggered.description=Die Anwendung wird in Kürze die Daten des Depots importieren.
 viewRepository.active.title=Aktiv
 viewRepository.url.title=URL
-viewRepository.deactiveAction.title=Deaktivieren
-viewRepository.reactiveAction.title=Reaktivieren
+viewRepository.deactivateAction.title=Deaktivieren
+viewRepository.reactivateAction.title=Reaktivieren
 viewRepository.triggerImportAction.title=Import anstoßen
 viewRepository.editAction.title=Bearbeiten

@@ -204,6 +212,8 @@
 viewUser.changePaswordAction.title=Kennwort ändern
 viewUser.editAction.title=Bearbeiten
 viewUser.logoutAction.title=Abmelden
+viewUser.deactivateAction.title=Deaktivieren
+viewUser.reactivateAction.title=Reaktivieren

 listPkgVersionsForPkg.latest=Neueste

@@ -213,7 +223,8 @@
 banner.action.user=Details für {0} anzeigen
 banner.action.logout={0} abmelden
 banner.action.naturalLanguagePrefix=Sprache:
-banner.action.repositories=Paket-Depots
+banner.action.repositories=Paket-Depots anzeigen
+banner.action.users=Benutzer anzeigen

 naturalLanguage.en=English
 naturalLanguage.de=Deutsch
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/application-context.xml Wed Jun 4 11:36:29 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/application-context.xml Mon Jun 23 10:01:05 2014 UTC
@@ -13,7 +13,7 @@
     -->

     <bean
- class="org.haikuos.haikudepotserver.userrating.UserRatingDerivationService" + class="org.haikuos.haikudepotserver.userrating.LocalUserRatingDerviationService"
             init-method="startAsyncAndAwaitRunning"
             destroy-method="stopAsyncAndAwaitTerminated">
     </bean>
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sat Jun 21 11:24:37 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Mon Jun 23 10:01:05 2014 UTC
@@ -91,6 +91,7 @@
<value>/js/app/controller/paginationcontrolplaygroundcontroller.js</value> <value>/js/app/controller/viewuserratingcontroller.js</value> <value>/js/app/controller/listpkgversionsforpkgcontroller.js</value> + <value>/js/app/controller/listuserscontroller.js</value>

<value>/js/app/service/jsonrpcservice.js</value> <value>/js/app/service/messagesourceservice.js</value>
@@ -130,6 +131,7 @@
                             <value>/css/editpkgscreenshots.css</value>
<value>/css/editpkgversionlocalization.css</value>
                             <value>/css/addedituserrating.css</value>
+                            <value>/css/listusers.css</value>
                         </list>
                     </property>
                 </bean>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html Fri Mar 28 10:40:14 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepository.html Mon Jun 23 10:01:05 2014 UTC
@@ -25,12 +25,12 @@
     <ul>
<li ng-show="canDeactivate()" repository="repository" show-if-repository-permission="'REPOSITORY_EDIT'">
             <a href="" ng-click="goDeactivate()">
- <message key="viewRepository.deactiveAction.title"></message> + <message key="viewRepository.deactivateAction.title"></message>
             </a>
         </li>
- <li ng-show="canActivate()" repository="repository" show-if-repository-permission="'REPOSITORY_EDIT'">
-            <a href="" ng-click="goActivate()">
- <message key="viewRepository.reactiveAction.title"></message> + <li ng-show="canReactivate()" repository="repository" show-if-repository-permission="'REPOSITORY_EDIT'">
+            <a href="" ng-click="goReactivate()">
+ <message key="viewRepository.reactivateAction.title"></message>
             </a>
         </li>
<li ng-show="repository.active" repository="repository" show-if-repository-permission="'REPOSITORY_IMPORT'">
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js Thu Jun 19 09:19:46 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewrepositorycontroller.js Mon Jun 23 10:01:05 2014 UTC
@@ -46,7 +46,7 @@
                 );
             }

-            $scope.canActivate = function() {
+            $scope.canReactivate = function() {
return $scope.repository && !$scope.repository.active && !amUpdatingActive;
             };

@@ -54,7 +54,7 @@
return $scope.repository && $scope.repository.active && !amUpdatingActive;
             };

-            $scope.goActivate = function() {
+            $scope.goReactivate = function() {
                 updateActive(true);
             };

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewuser.html Sat Mar 29 10:27:45 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewuser.html Mon Jun 23 10:01:05 2014 UTC
@@ -20,6 +20,16 @@
     <h2><message key="gen.actions.title"></message></h2>

     <ul>
+ <li ng-show="canDeactivate()" user="user" show-if-user-permission="'USER_EDIT'">
+            <a href="" ng-click="goDeactivate()">
+                <message key="viewUser.deactivateAction.title"></message>
+            </a>
+        </li>
+ <li ng-show="canReactivate()" user="user" show-if-user-permission="'USER_EDIT'">
+            <a href="" ng-click="goReactivate()">
+                <message key="viewUser.reactivateAction.title"></message>
+            </a>
+        </li>
         <li user="user" show-if-user-permission="'USER_CHANGEPASSWORD'">
             <a href="" ng-click="goChangePassword()">
<message key="viewUser.changePaswordAction.title"></message>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewusercontroller.js Thu Jun 19 09:19:46 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewusercontroller.js Mon Jun 23 10:01:05 2014 UTC
@@ -66,6 +66,48 @@
                 breadcrumbs.reset();
                 $window.location.href = '/';
             }
+
+            $scope.canDeactivate = function() {
+                return $scope.user &&
+                    $scope.user.active &&
+                    $scope.user.nickname != userState.user().nickname;
+            }
+
+            $scope.canReactivate = function() {
+                return $scope.user &&
+                    !$scope.user.active &&
+                    $scope.user.nickname != userState.user().nickname;
+            }
+
+            function setActive(flag) {
+                $log.info('will update user active flag; '+flag);
+
+                jsonRpc.call(
+                    constants.ENDPOINT_API_V1_USER,
+                    "updateUser",
+                    [{
+                        filter : [ 'ACTIVE' ],
+                        nickname : $scope.user.nickname,
+                        active : flag
+                    }]
+                ).then(
+                    function(result) {
+                        $scope.user.active = flag;
+                        $log.info('did update user active flag; '+flag);
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+            }
+
+            $scope.goDeactivate = function() {
+                setActive(false);
+            }
+
+            $scope.goReactivate = function() {
+                setActive(true);
+            }

         }
     ]
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewuserrating.html Mon Jun 2 11:04:38 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewuserrating.html Mon Jun 23 10:01:05 2014 UTC
@@ -31,8 +31,8 @@
<message key="viewUserRating.deactiveAction.title"></message>
             </a>
         </li>
- <li ng-show="canActivate()" user-rating="userRating" show-if-user-rating-permission="'USERRATING_EDIT'">
-            <a href="" ng-click="goActivate()">
+ <li ng-show="canReactivate()" user-rating="userRating" show-if-user-rating-permission="'USERRATING_EDIT'">
+            <a href="" ng-click="goReactivate()">
<message key="viewUserRating.reactiveAction.title"></message>
             </a>
         </li>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewuserratingcontroller.js Thu Jun 19 09:19:46 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewuserratingcontroller.js Mon Jun 23 10:01:05 2014 UTC
@@ -63,11 +63,11 @@
                 return $scope.userRating && $scope.userRating.active;
             };

-            $scope.canActivate = function() {
+            $scope.canReactivate = function() {
                 return $scope.userRating && !$scope.userRating.active;
             };

-            $scope.goActivate = function() {
+            $scope.goReactivate = function() {
                 setActive(true);
             };

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/banner.html Thu Jun 19 10:17:07 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/banner.html Mon Jun 23 10:01:05 2014 UTC
@@ -91,6 +91,12 @@
                 </a>
             </li>

+ <li ng-show="canGoListUsers()" show-if-permission="'USER_LIST'">
+                <a href="" ng-click="goListUsers()">
+                    <message key="banner.action.users"></message>
+                </a>
+            </li>
+
         </ul>

     </modal-container>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/bannerdirective.js Thu Jun 19 10:17:07 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/bannerdirective.js Mon Jun 23 10:01:05 2014 UTC
@@ -206,6 +206,20 @@
                         ]);
                         $scope.showActions = false;
                     };
+
+ // not from a permissions perspective, but from a navigational perspective.
+                    $scope.canGoListUsers = function() {
+                        var p = $location.path();
+                        return '/users' != p;
+                    }
+
+                    $scope.goListUsers = function() {
+                        breadcrumbs.resetAndNavigate([
+                            breadcrumbs.createHome(),
+                            breadcrumbs.createListUsers()
+                        ]);
+                        $scope.showActions = false;
+                    }

                     // -----------------
                     // EVENT HANDLING
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifpermissiondirective.js Tue Jun 10 11:21:21 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifpermissiondirective.js Mon Jun 23 10:01:05 2014 UTC
@@ -39,6 +39,8 @@
                         null,
                         null);
                 }
+
+                $scope.$on('userChangeSuccess', function() { check(); });

             }
         }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifpkgpermissiondirective.js Tue Jun 10 11:21:21 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifpkgpermissiondirective.js Mon Jun 23 10:01:05 2014 UTC
@@ -46,6 +46,8 @@
                             'PKG',
                             pkg ? pkg.name : undefined);
                     }
+
+                $scope.$on('userChangeSuccess', function() { check(); });

             }
         }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifrepositorypermissiondirective.js Tue Jun 10 11:21:21 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifrepositorypermissiondirective.js Mon Jun 23 10:01:05 2014 UTC
@@ -46,6 +46,8 @@
                         'REPOSITORY',
                         repository ? repository.code : undefined);
                 }
+
+                $scope.$on('userChangeSuccess', function() { check(); });

             }
         }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifuserpermissiondirective.js Tue Jun 10 11:21:21 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifuserpermissiondirective.js Mon Jun 23 10:01:05 2014 UTC
@@ -46,6 +46,8 @@
                         'USER',
                         user ? user.nickname : undefined);
                 }
+
+                $scope.$on('userChangeSuccess', function() { check(); });

             }
         }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifuserratingpermissiondirective.js Tue Jun 10 11:21:21 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/showifuserratingpermissiondirective.js Mon Jun 23 10:01:05 2014 UTC
@@ -46,6 +46,8 @@
                         'USERRATING',
                         userRating ? userRating.code : undefined);
                 }
+
+                $scope.$on('userChangeSuccess', function() { check(); });

             }
         }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/userlabeldirective.js Wed May 28 12:18:40 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/userlabeldirective.js Mon Jun 23 10:01:05 2014 UTC
@@ -7,31 +7,62 @@
* <p>This directive renders a small piece of text and maybe a hyperlink to show a user.</p>
  */

-angular.module('haikudepotserver').directive('userLabel',function() {
-    return {
-        restrict: 'E',
-        link : function($scope,element,attributes) {
+angular.module('haikudepotserver').directive('userLabel',[
+    'standardDirectiveMixins','breadcrumbs',
+    function(standardDirectiveMixins,breadcrumbs) {
+        return {
+            restrict: 'E',
+            link : function($scope,element,attributes) {

-            var userExpression = attributes['user'];
+                // apply a mixin for standard directive mixins.
+                angular.extend(this,standardDirectiveMixins);

-            if(!userExpression || !userExpression.length) {
-                throw 'expected expression for "user"';
-            }
+                var userExpression = attributes['user'];
+                var shouldLink = attributes['shouldLink'];
+                var userBreadcrumbItem = undefined;
+
+                if(!userExpression || !userExpression.length) {
+                    throw 'expected expression for "user"';
+                }
+
+                var containerEl = angular.element('<span></span>');
+                var textTargetEl = containerEl;
+                element.replaceWith(containerEl);
+
+ if((undefined == shouldLink || 'true' == shouldLink) && !isChildOfForm(containerEl)) {
+                    var hyperlinkEl = angular.element('<a href=""></a>');
+                    containerEl.append(hyperlinkEl);
+                    textTargetEl = hyperlinkEl;
+
+                    hyperlinkEl.on('click', function(el) {
+                        if(userBreadcrumbItem) {
+                            $scope.$apply(function() {
+ breadcrumbs.pushAndNavigate(userBreadcrumbItem);
+                            });
+                        }
+                        el.preventDefault();
+                    });
+                }

-            var containerEl = angular.element('<span></span>');
-            element.replaceWith(containerEl);
+                function refresh(user) {

-            function refresh(user) {
-                containerEl.text(user ? user.nickname : '');
-            }
+                    if (!user) {
+                        textTargetEl.text('');
+                        userBreadcrumbItem = undefined;
+                    }
+                    else {
+ userBreadcrumbItem = breadcrumbs.createViewUser(user);
+                        textTargetEl.text(user.nickname);
+                    }
+                }

-            $scope.$watch(userExpression, function(newValue) {
-                refresh(newValue);
-            });
+                $scope.$watch(userExpression, function(newValue) {
+                    refresh(newValue);
+                });

-            refresh($scope.$eval(userExpression));
+                refresh($scope.$eval(userExpression));

+            }
         }
-    };

-});
+    }]);
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Sat Jun 21 11:24:37 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Mon Jun 23 10:01:05 2014 UTC
@@ -19,6 +19,7 @@
.when('/runtimeinformation',{controller:'RuntimeInformationController', templateUrl:'/js/app/controller/runtimeinformation.html'}) .when('/about',{controller:'AboutController', templateUrl:'/js/app/controller/about.html'}) .when('/authenticateuser',{controller:'AuthenticateUserController', templateUrl:'/js/app/controller/authenticateuser.html'}) + .when('/users',{controller:'ListUsersController', templateUrl:'/js/app/controller/listusers.html'}) .when('/users/add',{controller:'CreateUserController', templateUrl:'/js/app/controller/createuser.html'}) .when('/user/:nickname',{controller:'ViewUserController', templateUrl:'/js/app/controller/viewuser.html'}) .when('/user/:nickname/edit',{controller:'EditUserController', templateUrl:'/js/app/controller/edituser.html'})
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Sat Jun 21 11:24:37 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Mon Jun 23 10:01:05 2014 UTC
@@ -642,6 +642,13 @@
                     });
                 },

+                createListUsers : function() {
+                    return applyDefaults({
+                        titleKey : 'breadcrumb.listUsers.title',
+                        path : '/users/'
+                    });
+                },
+
                 createChangePassword : function(user) {
                     return applyDefaults({
                         titleKey : 'breadcrumb.changePassword.title',
=======================================
--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/api1/UserApiIT.java Mon Apr 21 10:48:33 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/api1/UserApiIT.java Mon Jun 23 10:01:05 2014 UTC
@@ -9,6 +9,7 @@
 import org.apache.cayenne.ObjectContext;
 import org.fest.assertions.Assertions;
 import org.haikuos.haikudepotserver.api1.model.user.*;
+import org.haikuos.haikudepotserver.api1.support.AbstractSearchRequest;
 import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
 import org.haikuos.haikudepotserver.captcha.CaptchaService;
 import org.haikuos.haikudepotserver.captcha.model.Captcha;
@@ -34,7 +35,7 @@

         {
             ObjectContext context = serverRuntime.getContext();
- User user = integrationTestSupportService.createBasicUser(context, "testuser", "yUe4o2Nwe009"); // language is english + integrationTestSupportService.createBasicUser(context, "testuser", "yUe4o2Nwe009"); // language is english
             setAuthenticatedUser("testuser");
         }

@@ -155,5 +156,45 @@
Assertions.assertThat(authenticationService.authenticate("testuser","8R3nlp11gX").get()).isEqualTo(user.getObjectId());

     }
+
+    @Test
+    public void testSearchUsers() {
+
+        setAuthenticatedUserToRoot();
+
+        {
+            ObjectContext context = serverRuntime.getContext();
+ integrationTestSupportService.createBasicUser(context, "onehunga", "U7vqpsu6BB"); + integrationTestSupportService.createBasicUser(context, "mangere", "U7vqpsu6BB"); + integrationTestSupportService.createBasicUser(context, "avondale", "U7vqpsu6BB"); + integrationTestSupportService.createBasicUser(context, "remuera", "U7vqpsu6BB"); + integrationTestSupportService.createBasicUser(context, "freemansbay", "U7vqpsu6BB"); + integrationTestSupportService.createBasicUser(context, "kohimarama", "U7vqpsu6BB"); + integrationTestSupportService.createBasicUser(context, "mtwellington", "U7vqpsu6BB"); + integrationTestSupportService.createBasicUser(context, "mtalbert", "U7vqpsu6BB"); + integrationTestSupportService.createBasicUser(context, "kingsland", "U7vqpsu6BB");
+        }
+
+        SearchUsersRequest request = new SearchUsersRequest();
+        request.limit = 2;
+        request.offset = 0;
+        request.includeInactive = true;
+        request.expression = "er";
+ request.expressionType = AbstractSearchRequest.ExpressionType.CONTAINS;
+
+        // ------------------------------------
+        SearchUsersResult result = userApi.searchUsers(request);
+        // ------------------------------------
+
+        // we should select from mangere, remuera, mtalbert and only see
+        // mangere, mtalbert in that order.
+
+        Assertions.assertThat(result.total).isEqualTo(3);
+        Assertions.assertThat(result.items.size()).isEqualTo(2);
+ Assertions.assertThat(result.items.get(0).nickname).isEqualTo("mangere"); + Assertions.assertThat(result.items.get(0).active).isEqualTo(Boolean.TRUE); + Assertions.assertThat(result.items.get(1).nickname).isEqualTo("mtalbert");
+
+    }

 }
=======================================
--- /haikudepotserver-webapp/src/test/resources/spring/test.xml Wed Jun 4 11:36:29 2014 UTC +++ /haikudepotserver-webapp/src/test/resources/spring/test.xml Mon Jun 23 10:01:05 2014 UTC
@@ -9,4 +9,7 @@
<bean class="org.haikuos.haikudepotserver.IntegrationTestSupportService">
     </bean>

+ <bean class="org.haikuos.haikudepotserver.userrating.NoopUserRatingDerviationService">
+    </bean>
+
 </beans>

==============================================================================
Revision: b13dbbc35447
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jun 28 10:23:44 2014 UTC
Log:      + implement token bearer authentication

http://code.google.com/p/haiku-depot-web-app/source/detail?r=b13dbbc35447

Added:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/RenewTokenRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/RenewTokenResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/web/ErrorFilter.java
 /haikudepotserver-webapp/src/main/webapp/img/haikudepot-error.svg
Deleted:
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/error.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/errorcontroller.js
Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserResult.java
 /haikudepotserver-docs/src/main/latex/docs/part-api.tex
 /haikudepotserver-docs/src/main/latex/docs/part-config.tex
 /haikudepotserver-docs/src/main/latex/docs/part-deployment.tex
 /haikudepotserver-parent/pom.xml
 /haikudepotserver-rpm/src/main/etc/config__config.properties
 /haikudepotserver-webapp/pom.xml
/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/User.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AbstractUserAuthenticationAware.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthenticationFilter.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthenticationService.java
 /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml
 /haikudepotserver-webapp/src/main/webapp/WEB-INF/web.xml
/haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateusercontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/routes.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/haikudepotserver/AbstractIntegrationTest.java /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/api1/UserApiIT.java
 /haikudepotserver-webapp/src/test/resources/local.properties

=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/RenewTokenRequest.java Sat Jun 28 10:23:44 2014 UTC
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class RenewTokenRequest {
+
+    public String token;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/RenewTokenResult.java Sat Jun 28 10:23:44 2014 UTC
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class RenewTokenResult {
+
+    public String token;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/web/ErrorFilter.java Sat Jun 28 10:23:44 2014 UTC
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.support.web;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.html.HtmlEscapers;
+import com.google.common.net.MediaType;
+import org.haikuos.haikudepotserver.api1.support.Constants;
+import org.haikuos.haikudepotserver.dataobjects.NaturalLanguage;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * <p>This filter gets hit whenever anything goes wrong in the application from a user perspective. It will + * say that a problem has arisen and off the user the opportunity to re-enter the application. It is as + * simple as possible to reduce the possibility of the error page failing as well.</p>
+ */
+
+public class ErrorFilter implements Filter {
+
+    private final static String PARAM_JSONRPCERRORCODE = "jrpcerrorcd";
+
+    private final static Map<String,String> PREFIX = ImmutableMap.of(
+            NaturalLanguage.CODE_ENGLISH, "Oh darn!",
+            NaturalLanguage.CODE_GERMAN, "Oh mei!");
+
+    private final static Map<String,String> BODY_GENERAL = ImmutableMap.of(
+ NaturalLanguage.CODE_ENGLISH, "Something has gone wrong with your use of this web application.", + NaturalLanguage.CODE_GERMAN, "Etwas ist falsch gegangen mit Ihre Benutzung des Anwendungs.");
+
+ private final static Map<String,String> BODY_AUTHORIZATIONFAILURE = ImmutableMap.of( + NaturalLanguage.CODE_ENGLISH, "Your authentication with the service is expired or you have reached a page that is not accessible with the level of your permissions.", + NaturalLanguage.CODE_GERMAN, "Die Berechtigungen für diesen Dienst sind abgelaufen, oder der Zugang zur angeforderten Seite erfordert zusätzliche Zugriffsrechte.");
+
+    private final static Map<String,String> ACTION = ImmutableMap.of(
+            NaturalLanguage.CODE_ENGLISH, "Start again",
+            NaturalLanguage.CODE_GERMAN, "Neue anfangen");
+
+    private byte[] pageGeneralBytes = null;
+
+    private Map<String,String> deriveBody(Integer jsonRpcErrorCode) {
+ if (null != jsonRpcErrorCode && Constants.ERROR_CODE_AUTHORIZATIONFAILURE == jsonRpcErrorCode) {
+            return BODY_AUTHORIZATIONFAILURE;
+        }
+        return BODY_GENERAL;
+    }
+
+ private void messageLineAssembly(String naturalLanguageCode, StringBuilder out, Integer jsonRpcErrorCode) {
+        HtmlEscapers.htmlEscaper();
+        out.append("<div class=\"error-message-container\">\n");
+        out.append("<div class=\"error-message\">\n");
+        out.append("<strong>");
+ out.append(HtmlEscapers.htmlEscaper().escape(PREFIX.get(naturalLanguageCode)));
+        out.append("</strong>");
+        out.append(" ");
+ out.append(HtmlEscapers.htmlEscaper().escape(deriveBody(jsonRpcErrorCode).get(naturalLanguageCode)));
+        out.append("</div>\n");
+        out.append("<div class=\"error-startagain\">");
+        out.append(" &#8594; ");
+        out.append("<a href=\"/\">");
+ out.append(HtmlEscapers.htmlEscaper().escape(ACTION.get(naturalLanguageCode)));
+        out.append("</a>");
+        out.append("</div>\n");
+        out.append("</div>\n");
+    }
+
+    /**
+ * <p>Assemble the page using code in order to reduce the chance of things going wrong loading resources and so
+     * on.</p>
+     */
+
+ private void pageAssembly(StringBuilder out, Integer jsonRpcErrorCode) {
+
+        out.append("<html>\n");
+        out.append("<head>\n");
+        out.append("<title>HaikuDepotServer - Error</title>\n");
+        out.append("<style>\n");
+ out.append("body { background-color: #336698; position: relative; font-family: sans-serif; }\n");
+        out.append("h1 { text-align: center; }\n");
+ out.append("#error-container { color: white; width: 420px; height: 320px; margin:0 auto; margin-top: 72px; }\n"); + out.append("#error-container .error-message-container { margin-bottom: 20px; }\n"); + out.append("#error-container .error-startagain { text-align: right; }\n"); + out.append("#error-container .error-startagain > a { color: white; }\n");
+        out.append("#error-image { text-align: center; }\n");
+        out.append("</style>\n");
+        out.append("</head>\n");
+        out.append("<body>\n");
+        out.append("<div id=\"error-container\">\n");
+ out.append("<div id=\"error-image\"><img src=\"/img/haikudepot-error.svg\"></div>\n");
+        out.append("<h1>Haiku Depot Server</h1>\n");
+
+        for(String naturalLanguageCode : new String[] {
+                NaturalLanguage.CODE_ENGLISH,
+                NaturalLanguage.CODE_GERMAN }) {
+ messageLineAssembly(naturalLanguageCode, out, jsonRpcErrorCode);
+        }
+
+        out.append("</div>\n");
+        out.append("</body>\n");
+        out.append("</html>\n");
+    }
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+
+ // get together the basic general page as a fallback that exists in memory so that in the worst
+        // case scenario, if memory is tight at least we can output this.
+
+        StringBuilder out = new StringBuilder();
+        pageAssembly(out, null);
+        pageGeneralBytes = out.toString().getBytes(Charsets.UTF_8);
+    }
+
+    @Override
+    public void doFilter(
+            ServletRequest request,
+            ServletResponse response,
+            FilterChain chain) throws IOException, ServletException {
+
+        try {
+            HttpServletRequest httpRequest = (HttpServletRequest) request;
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+            httpResponse.setContentType(MediaType.HTML_UTF_8.toString());
+ String jsonRpcErrorCodeString = httpRequest.getParameter(PARAM_JSONRPCERRORCODE);
+
+            byte pageBytes[] = pageGeneralBytes;
+
+            if(!Strings.isNullOrEmpty(jsonRpcErrorCodeString)) {
+
+                try {
+ Integer jsonRpcErrorCode = Integer.parseInt(jsonRpcErrorCodeString);
+                    StringBuilder out = new StringBuilder();
+                    pageAssembly(out, jsonRpcErrorCode);
+                    pageBytes = out.toString().getBytes(Charsets.UTF_8);
+                }
+                catch(Throwable th) {
+                    pageBytes = pageGeneralBytes;
+                }
+
+            }
+
+            httpResponse.setContentLength(pageBytes.length);
+            httpResponse.getOutputStream().write(pageBytes);
+            httpResponse.getOutputStream().flush();
+
+        }
+        catch(Throwable th) {
+            // eat it.
+        }
+
+    }
+
+    @Override
+    public void destroy() {
+        pageGeneralBytes = null;
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/img/haikudepot-error.svg Sat Jun 28 10:23:44 2014 UTC
@@ -0,0 +1,781 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:xlink="http://www.w3.org/1999/xlink";
+   version="1.1"
+   width="64"
+   height="64"
+   id="svg3648">
+  <metadata
+     id="metadata3791">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs3789">
+    <linearGradient
+       x1="46.07"
+       y1="78.239998"
+       x2="23.93"
+       y2="73.150002"
+       id="gradient0"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3657"
+         style="stop-color:#a3784b;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3659"
+         style="stop-color:#76401c;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="44.470001"
+       y1="70.75"
+       x2="29.889999"
+       y2="79.360001"
+       id="gradient1"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3666"
+         style="stop-color:#be8852;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3668"
+         style="stop-color:#dca977;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="54.080002"
+       y1="49.540001"
+       x2="50.66"
+       y2="59.950001"
+       id="gradient2"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3673"
+         style="stop-color:#7c4e28;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3675"
+         style="stop-color:#a17545;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="44.25"
+       y1="2.48"
+       x2="55.009998"
+       y2="17.280001"
+       id="gradient3"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3682"
+         style="stop-color:#663200;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3684"
+         style="stop-color:#834f1c;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="42.68"
+       y1="-29.809999"
+       x2="61.509998"
+       y2="-25.25"
+       id="gradient4"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3689"
+         style="stop-color:#e5b07b;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3691"
+         style="stop-color:#ffce9e;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="32.240002"
+       y1="-14.37"
+       x2="49.830002"
+       y2="-16.209999"
+       id="gradient5"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3700"
+         style="stop-color:#fccc9d;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3702"
+         style="stop-color:#eeb781;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="26.68"
+       y1="46.52"
+       x2="34.48"
+       y2="36.389999"
+       id="gradient6"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3711"
+         style="stop-color:#c6d7f5;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3713"
+         style="stop-color:#6b94dd;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="26.68"
+       y1="46.52"
+       x2="34.48"
+       y2="36.389999"
+       id="gradient7"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3722"
+         style="stop-color:#c6d7f5;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3724"
+         style="stop-color:#6b94dd;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="70.400002"
+       y1="34.52"
+       x2="71.080002"
+       y2="50.830002"
+       id="gradient8"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3735"
+         style="stop-color:#a3043c;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3737"
+         style="stop-color:#ffdce6;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="138.84"
+       y1="49.93"
+       x2="86.239998"
+       y2="116.1"
+       id="gradient9"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3744"
+         style="stop-color:#c13e3e;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3746"
+         style="stop-color:#e27a7a;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="-19.870001"
+       y1="-1.53"
+       x2="5.48"
+       y2="-26.290001"
+       id="gradient10"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3753"
+         style="stop-color:#ffec4b;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3755"
+         style="stop-color:#f0a506;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="30.5"
+       y1="-22.809999"
+       x2="56.009998"
+       y2="-9.1300001"
+       id="gradient11"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3760"
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3762"
+         style="stop-color:#fff289;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="53.009998"
+       y1="-9.5500002"
+       x2="69.940002"
+       y2="3.4300001"
+       id="gradient12"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3767"
+         style="stop-color:#f99b05;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3769"
+         style="stop-color:#fcb23d;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="43.439999"
+       y1="10.75"
+       x2="62.48"
+       y2="20.219999"
+       id="gradient13"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3774"
+         style="stop-color:#ffd5ac;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3776"
+         style="stop-color:#b07842;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="1.22"
+       y1="-19.6"
+       x2="44.889999"
+       y2="-14.22"
+       id="gradient14"
+       gradientUnits="userSpaceOnUse">
+      <stop
+         id="stop3781"
+         style="stop-color:#ffe3c7;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop3783"
+         style="stop-color:#f3b476;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="1.22"
+       y1="-19.6"
+       x2="44.889999"
+       y2="-14.22"
+       id="linearGradient3939"
+       xlink:href="#gradient14"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       x1="43.439999"
+       y1="10.75"
+       x2="62.48"
+       y2="20.219999"
+       id="linearGradient3942"
+       xlink:href="#gradient13"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       x1="53.009998"
+       y1="-9.5500002"
+       x2="69.940002"
+       y2="3.4300001"
+       id="linearGradient3945"
+       xlink:href="#gradient12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.5517,0,0,0.5517,14,9.0908)" />
+    <linearGradient
+       x1="30.5"
+       y1="-22.809999"
+       x2="56.009998"
+       y2="-9.1300001"
+       id="linearGradient3948"
+       xlink:href="#gradient11"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.5517,0,0,0.5517,14,9.0908)" />
+    <linearGradient
+       x1="-19.870001"
+       y1="-1.53"
+       x2="5.48"
+       y2="-26.290001"
+       id="linearGradient3951"
+       xlink:href="#gradient10"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.5517,0,0,0.5517,14,9.0908)" />
+    <linearGradient
+       x1="138.84"
+       y1="49.93"
+       x2="86.239998"
+       y2="116.1"
+       id="linearGradient3955"
+       xlink:href="#gradient9"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.5517,0,0,0.5517,14,9.0908)" />
+    <linearGradient
+       x1="70.400002"
+       y1="34.52"
+       x2="71.080002"
+       y2="50.830002"
+       id="linearGradient3959"
+       xlink:href="#gradient8"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.5517,0,0,0.5517,20.6206,20.1253)" />
+    <linearGradient
+       x1="26.68"
+       y1="46.52"
+       x2="34.48"
+       y2="36.389999"
+       id="linearGradient3965"
+       xlink:href="#gradient7"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.5517,0,0,0.5517,14,9.0908)" />
+    <linearGradient
+       x1="26.68"
+       y1="46.52"
+       x2="34.48"
+       y2="36.389999"
+       id="linearGradient3970"
+       xlink:href="#gradient6"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.5517,0,0,0.5517,5.1724,20.1253)" />
+    <linearGradient
+       x1="32.240002"
+       y1="-14.37"
+       x2="49.830002"
+       y2="-16.209999"
+       id="linearGradient3975"
+       xlink:href="#gradient5"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       x1="42.68"
+       y1="-29.809999"
+       x2="61.509998"
+       y2="-25.25"
+       id="linearGradient3980"
+       xlink:href="#gradient4"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       x1="44.25"
+       y1="2.48"
+       x2="55.009998"
+       y2="17.280001"
+       id="linearGradient3983"
+       xlink:href="#gradient3"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       x1="54.080002"
+       y1="49.540001"
+       x2="50.66"
+       y2="59.950001"
+       id="linearGradient3987"
+       xlink:href="#gradient2"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       x1="44.470001"
+       y1="70.75"
+       x2="29.889999"
+       y2="79.360001"
+       id="linearGradient3990"
+       xlink:href="#gradient1"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       x1="46.07"
+       y1="78.239998"
+       x2="23.93"
+       y2="73.150002"
+       id="linearGradient3994"
+       xlink:href="#gradient0"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4017">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4019" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4040">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4042" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4044">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4046" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4048">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4050" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4056">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4058" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4060">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4062" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4064">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4066" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4068">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4070" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4072">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4074" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4076">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4078" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4080">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4082" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4084">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4086" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4088">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4090" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4092">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4094" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4096">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4098" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4100">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4102" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4104">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4106" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4108">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4110" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4112">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4114" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4116">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4118" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4124">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4126" />
+    </filter>
+    <filter
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       color-interpolation-filters="sRGB"
+       id="filter4128">
+      <feColorMatrix
+         values="0"
+         type="saturate"
+         id="feColorMatrix4130" />
+    </filter>
+  </defs>
+  <path
+     d="m 32,62 h 8 L 44,64 61,43 55.8,40.4 60,38 52,37 32,62 z"
+     id="path3652"
+     style="fill:#000000;fill-opacity:0.39599998" />
+  <path
+     d="M 10,39 V 49 L 32,60 50,42 v -6"
+     id="path3654"
+ style="fill:none;stroke:#000000;stroke-width:4;stroke-linejoin:round" />
+  <path
+     d="M 32,38 50,20 V 42 L 32,60 V 38 z"
+     id="path3661"
+     style="fill:url(#linearGradient3994);filter:url(#filter4064)" />
+  <path
+     d="M 32,38 H 42 L 32,48 V 38 z"
+     id="path3663"
+     style="fill:#5d2b0c;filter:url(#filter4060)" />
+  <path
+     d="M 11.89,36.01 14,46 32,55 v 5 L 10,49 V 35.99 l 1.89,0.02 z"
+     id="path3670"
+     style="fill:url(#linearGradient3990);filter:url(#filter4072)" />
+  <path
+     d="M 32,38 V 55 L 14,46 11.89,36.01 32,38 z"
+     id="path3677"
+     style="fill:url(#linearGradient3987);filter:url(#filter4068)" />
+  <path
+ d="M 24,46 2,35 10,27 H 2 L 24,14 h 6 l 7,-8 17,6 -4,11 12,3 -18,18 -12,-6 -8,8 z"
+     id="path3679"
+ style="fill:none;stroke:#000000;stroke-width:4;stroke-linejoin:round" />
+  <path
+     d="M 10,27 30,14 V 33 L 23.89,37.88 10,30.99 V 27 z"
+     id="path3686"
+     style="fill:url(#linearGradient3983);filter:url(#filter4128)" />
+  <path
+     d="m 30,14 4,16 16,7 V 22 L 30,14 z"
+     id="path3693"
+     style="fill:url(#linearGradient3980);filter:url(#filter4056)" />
+  <path
+     d="m 30,14 4,16 16,7 -8.15,1.33 L 30,33 V 14 z"
+     id="path3695"
+     style="fill:#945f2c" />
+  <path
+     d="M 30,33 23.89,37.88 32,38 36.1,35.74 30,33 z"
+     id="path3697"
+     style="fill:#85501c" />
+  <path
+     d="M 37,6 54,12 50,23 30,14 37,6 z"
+     id="path3704"
+     style="fill:url(#linearGradient3975);filter:url(#filter4017)" />
+  <path
+     d="M 2,27 24,14 h 6 L 10,27 H 2 z"
+     id="path3706"
+     style="fill:#ffdcba;filter:url(#filter4048)" />
+  <path
+ d="m 15.103,27.8491 v 8.8272 l 8.8272,4.4136 5.517,-5.517 v -8.8272 l -8.2755,-3.3102 -6.0687,4.4136 z"
+     id="path3708"
+     style="fill:none;stroke:#000000;stroke-width:2.20679998" />
+  <path
+ d="m 15.103,27.8491 v 8.8272 l 8.8272,4.4136 V 31.711 L 15.103,27.8491 z"
+     id="path3715"
+     style="fill:url(#linearGradient3970);filter:url(#filter4116)" />
+  <path
+ d="m 23.9302,31.711 v 9.3789 l 5.517,-5.517 v -8.8272 l -5.517,4.9653 z"
+     id="path3717"
+     style="fill:#003cb0;filter:url(#filter4080)" />
+  <path
+     d="m 27.2408,28.4004 2.2068,6.0687 v -4.9653 l -2.2068,-1.1034 z"
+     id="path3719"
+     style="fill:#0d2964;filter:url(#filter4084)" />
+  <path
+ d="m 15.1034,27.8486 8.8272,3.8619 5.517,-4.9653 -8.2755,-3.3102 -6.0687,4.4136 z"
+     id="path3726"
+     style="fill:url(#linearGradient3965);filter:url(#filter4076)" />
+  <path
+ d="m 30.5512,27.8491 v 8.8272 l 8.8272,4.4136 5.517,-5.517 v -8.8272 l -8.2755,-3.3102 -6.0687,4.4136 z"
+     id="path3728"
+     style="fill:none;stroke:#000000;stroke-width:2.20679998" />
+  <path
+ d="m 32.7578,32.8139 -0.0055,4.959783 6.6259,3.315717 V 31.7105 L 36.658319,30.518828 32.7578,32.8139 z"
+     id="path3730"
+     style="fill:#ec6666;filter:url(#filter4108)" />
+  <path
+ d="m 30.551,27.8486 v 8.8272 L 32.752283,37.773683 32.7578,32.8139 36.658319,30.518828 30.551,27.8486 z"
+     id="path3732"
+     style="fill:#cd4d4d;filter:url(#filter4104)" />
+  <path
+ d="m 39.3784,31.711 v 9.3789 l 5.517,-5.517 v -8.8272 l -5.517,4.9653 z"
+     id="path3739"
+     style="fill:url(#linearGradient3959);filter:url(#filter4124)" />
+  <path
+ d="m 36.658319,30.518828 2.719881,1.191672 5.517,-4.9653 -2.764017,-1.108917 -5.472864,4.882545 z"
+     id="path3741"
+     style="fill:#ffacac;filter:url(#filter4112)" />
+  <path
+ d="m 30.551,27.8486 6.107319,2.670228 5.472864,-4.882545 L 36.6197,23.435 30.551,27.8486 z"
+     id="path3748"
+     style="fill:url(#linearGradient3955);filter:url(#filter4088)" />
+  <path
+ d="m 23.9306,16.8146 v 8.8272 l 8.8272,4.4136 5.517,-5.517 v -8.8272 l -8.2755,-3.3102 -6.0687,4.4136 z"
+     id="path3750"
+     style="fill:none;stroke:#000000;stroke-width:2.20679998" />
+  <path
+ d="m 23.9306,16.8146 v 8.8272 l 8.8272,4.4136 v -9.3789 l -8.8272,-3.8619 z"
+     id="path3757"
+     style="fill:url(#linearGradient3951);filter:url(#filter4096)" />
+  <path
+ d="m 23.9306,16.8146 8.8272,3.8619 5.517,-4.9653 -8.2755,-3.3102 -6.0687,4.4136 z"
+     id="path3764"
+     style="fill:url(#linearGradient3948);filter:url(#filter4092)" />
+  <path
+ d="m 32.7578,20.6765 v 9.3789 l 5.517,-5.517 v -8.8272 l -5.517,4.9653 z"
+     id="path3771"
+     style="fill:url(#linearGradient3945);filter:url(#filter4100)" />
+  <path
+     d="M 32,38 50,23 62,26 44,44 32,38 z"
+     id="path3778"
+     style="fill:url(#linearGradient3942);filter:url(#filter4044)" />
+  <path
+     d="M 2,35 24,46 32,38 10,27 2,35 z"
+     id="path3785"
+     style="fill:url(#linearGradient3939);filter:url(#filter4040)" />
+</svg>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/error.html Fri Mar 28 10:40:14 2014 UTC
+++ /dev/null
@@ -1,13 +0,0 @@
-<div class="content-container">
-
-    <div class="alert-container">
-        <div>
- <strong>Oh darn!</strong> Something has gone wrong with your use of this web application &#8594; <a href="/">Start again</a>
-        </div>
-        <div>
- <strong>Oh mei!</strong> Etwas ist falsch gegangen mit Ihre Benutzung des Anwendungs &#8594; <a href="/">Neue anfangen</a>
-        </div>
-    </div>
-
-</div>
-<div class="footer"></div>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/errorcontroller.js Tue Nov 19 09:57:43 2013 UTC
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright 2013, Andrew Lindesay
- * Distributed under the terms of the MIT License.
- */
-
-angular.module('haikudepotserver').controller(
-    'ErrorController',
-    [
-        '$scope','$log','$location','userState',
-        function(
-            $scope,$log,$location,userState) {
-               userState.user(null);
-        }
-    ]
-);
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java Mon Jun 23 10:01:05 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java Sat Jun 28 10:23:44 2014 UTC
@@ -44,12 +44,20 @@

     /**
* <p>This method will allow a client to authenticate against the server. If this is - * successful then the client will know that it is OK to use the authentication
-     * principal and credentials for further API calls.</p>
+ * successful then it will return a json web token that can be used for further API + * calls for some period of time. If it is unsuccessful then it will return null.
+     * </p>
      */

AuthenticateUserResult authenticateUser(AuthenticateUserRequest authenticateUserRequest);

+    /**
+ * <p>This method will renew the token supplied. If the token has expired then this
+     * method will return a null value for the token.</p>
+     */
+
+    RenewTokenResult renewToken(RenewTokenRequest renewTokenRequest);
+
     /**
* <p>This method will allow the client to modify the password of a user.</p>
      */
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserResult.java Fri Nov 15 08:51:45 2013 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserResult.java Sat Jun 28 10:23:44 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -7,6 +7,11 @@

 public class AuthenticateUserResult {

-    public Boolean authenticated;
+    /**
+ * <p>In the case of a successful authentication, this field will be non-null and will contain a standard + * formatted json-web-token. If the authentication had failed then this token will be null.</p>
+     */
+
+    public String token;

 }
=======================================
--- /haikudepotserver-docs/src/main/latex/docs/part-api.tex Mon Apr 7 11:46:03 2014 UTC +++ /haikudepotserver-docs/src/main/latex/docs/part-api.tex Sat Jun 28 10:23:44 2014 UTC
@@ -35,15 +35,30 @@

 \subsubsection{Authentication}

-Authentication of invocations uses basic authentication for both REST and JSON-RPC. This procedure involves an HTTP header being included in each invocation that identifies the user and also provides their password. The value of the header includes a base-64 encoded string of the username and password, separated by a colon. This is an example of such a header; +\fcolorbox{red}{white}{\parbox{\textwidth}{\color{red} In a production environment, transport to and from the application server {\bf must} use the ``https'' protocol in order to ensure that the payloads are not transmitted over networks in the clear. The payloads will contain sensitive authentication data.}}
+
+Authentication of invocations for both REST and JSON-RPC uses ``basic authentication'' or ``token bearer authentication''. The application does not support cookie-based authentication. As part of an API invocation, an HTTP status 401 or 403 response maybe returned should authorization checks fail. If the authentication fails then the request will continue unauthenticated; there is no `challenge' necessarily returned back to the caller. A special API exists to provide a means to test a username and password authentication for the purposes of a login panel or similar; {\it UserApi.authenticateUser(..)}. This method will return a token if the authentication was successful.
+
+\paragraph{Basic Authentication}
+
+This approach involves an HTTP header being included in each invocation that identifies the user and also provides their password. The value of the header includes a base-64 encoded string of the username and password, separated by a colon. This is an example of such a header;

 \framebox{\tt Authorization: Basic dXNlcjpwYXNzd29yZA}

-If the authentication fails then the request will continue unauthenticated; there is no `challenge' returned back to the caller. As part of the continuation of the invocation, an http 401 or 403 response maybe returned should authorization checks have failed. A special API exists to provide a means to test an authentication for the purposes of a login panel or similar.
+\paragraph{Token Bearer}
+\label{token-bearer-authentication}
+
+This is a system of authentication that relies on a token being supplied with a client API invocation to the server. The client will first use the API {\it UserApi.authenticateUser(..)} to initially authenticate and obtain a token. This token can then be used to authenticate further API calls by including it in an HTTP header. An (abbreviated) example of the header might be;
+
+\framebox{\tt Authorization: Bearer eyJhbGciOiJIUzI.eyJleHAiOjE0MDM5NDY.1ifnDTLvX3A}
+
+The format and structure of the token conforms with the {\it json web token} standard. These tokens are signed using a secret, will expire after a certain length of time and are specific to a given deployment environment. The deployment environment is identified by configuring an ``issuer''.
+
+See \ref{config} for further details on configuration of these values.

-The application does not support cookie-based authentication.
+As the token has an expiry, an API method {\it UserApi.renewToken(..)} exists to obtain a fresh token before the existing token expires. The expiry can be read from the token; read about {\it json web tokens} to find out how they work.

-In a production environment, it is important that the request and response payloads are transported over secure HTTP (https) in order to avoid the ``Authorization'' header from being intercepted. +The token bearer system of authentication has the advantage that a user's password is supplied by the user to the client software for the initial authentication, but subsequently the password does not need to reside in the client runtime for the remainder of that session.

 \subsection{JSON-RPC API}

=======================================
--- /haikudepotserver-docs/src/main/latex/docs/part-config.tex Fri Jun 6 11:10:53 2014 UTC +++ /haikudepotserver-docs/src/main/latex/docs/part-config.tex Sat Jun 28 10:23:44 2014 UTC
@@ -63,3 +63,19 @@
 \subsubsection{\tt webresourcegroup.separated}

Some web resources such as javascript file can be concatenated for efficiency. Setting this configuration key to {\tt true} will mean that such resources are delivered to browsers as separated files; less efficient, but easier to debug.
+
+\subsection{Token Bearer Authentication}
+
+See \ref{token-bearer-authentication} for details regarding this area.
+
+\subsubsection{\tt authentication.jws.sharedkey}
+
+This shared key is used to sign the authentication tokens transmitted between the server and the client. This value is optional. If it is not supplied then a random shared key will be used for each launch of the application server. Non-configured keys will not work where two or more load-balanced application servers are employed. Any random value can be used for this and a possible way to create values is to use the command {\tt uuidgen}.
+
+\subsubsection{\tt authentication.jws.issuer}
+
+This identifies the system that has produced a given token and is required. It should be a string with the suffix ``.hds''. For example ``{\tt staging.hds}''.
+
+\subsubsection{\tt authentication.jws.expiryseconds}
+
+This configuration value defines how many seconds a token will be able to be used before it expires. This value is optional and a sensible default will be employed in its absence.
=======================================
--- /haikudepotserver-docs/src/main/latex/docs/part-deployment.tex Sun Feb 9 10:38:52 2014 UTC +++ /haikudepotserver-docs/src/main/latex/docs/part-deployment.tex Sat Jun 28 10:23:44 2014 UTC
@@ -9,7 +9,7 @@

\fcolorbox{red}{white}{\parbox{\textwidth}{\color{red} The default database installs a user with the nickname of `root' with a known password of `p4mphl3t'. This password {\bf must} be changed before the system is made available over a network.}}

-The API-related HTTP traffic into the application server uses basic-authentication. This technique exposes the username and password as a base64 encoded string in the HTTP payload. For this reason it is advised that HTTP traffic to and from the application server be transported as secure HTTP (https) in order to prevent a third party from reading the HTTP headers and extracting users' authentication details. +\fcolorbox{red}{white}{\parbox{\textwidth}{\color{red}The API-related HTTP traffic into the application server uses basic or token bearer authentication. Either technique exposes authentication details in the request and response. For this reason it is advised that HTTP traffic to and from the application server be transported as secure HTTP (https) in order to prevent a third party from reading the HTTP headers and extracting this information.}}

Please see \ref{prerequisites} for prerequisites required for running this software and \ref{buildandrelease} for information about obtaining a build product and possibly also creating a release version. The build product that you will require for deployment can be found at;

=======================================
--- /haikudepotserver-parent/pom.xml    Sun Jun 15 09:24:04 2014 UTC
+++ /haikudepotserver-parent/pom.xml    Sat Jun 28 10:23:44 2014 UTC
@@ -13,10 +13,11 @@
         <spring.version>4.0.5.RELEASE</spring.version>
         <postgresql.version>9.3-1101-jdbc41</postgresql.version>

-        <web-angularjs.version>1.2.17</web-angularjs.version>
+        <web-angularjs.version>1.2.18</web-angularjs.version>
         <web-underscorejs.version>1.6.0-3</web-underscorejs.version>
         <web-momentjs.version>2.6.0-2</web-momentjs.version>

+        <nimbus-jose-jwt.version>2.26</nimbus-jose-jwt.version>
     </properties>

     <dependencyManagement>
@@ -83,6 +84,11 @@
                 <artifactId>jsp-api</artifactId>
                 <version>2.2</version>
             </dependency>
+            <dependency>
+                <groupId>com.nimbusds</groupId>
+                <artifactId>nimbus-jose-jwt</artifactId>
+                <version>${nimbus-jose-jwt.version}</version>
+            </dependency>

             <!-- DATABASE / PERSISTENCE -->
             <dependency>
=======================================
--- /haikudepotserver-rpm/src/main/etc/config__config.properties Mon Jun 2 11:04:38 2014 UTC +++ /haikudepotserver-rpm/src/main/etc/config__config.properties Sat Jun 28 10:23:44 2014 UTC
@@ -35,3 +35,33 @@
 # to be meaningful.

 userrating.aggregation.pkg.minratings=3
+
+# -------------------------------------------
+# web security
+
+# This is the number of seconds for which an authentication token is
+# valid for.
+
+#authentication.jws.expiryseconds=
+
+# This secret is used to sign the tokens used to communicate between
+# the client and the server.  This value should be very hard to guess.
+# The output of the command "uuidgen" would be appropriate.  This
+# value must be kept secret and not disclosed in public.
+# commented out to force the value to be considered
+
+#authentication.jws.sharedkey=
+
+# This value is used in the production and consumption of the tokens
+# between the client and the server.  It is intended that this is
+# consistent for a given deployment.  Sample values that may be
+# appropriate;
+#
+#   dev.hds
+#   prod.hds
+#   staging.hds
+#   testing.hds
+#
+# commented out to force the value to be considered
+
+#authentication.jws.issuer=
=======================================
--- /haikudepotserver-webapp/pom.xml    Sun Jun 15 09:24:04 2014 UTC
+++ /haikudepotserver-webapp/pom.xml    Sat Jun 28 10:23:44 2014 UTC
@@ -65,6 +65,10 @@
             <artifactId>jsp-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>com.nimbusds</groupId>
+            <artifactId>nimbus-jose-jwt</artifactId>
+        </dependency>

         <!-- DATABASE / PERSISTENCE -->
         <dependency>
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Mon Jun 23 10:01:05 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Sat Jun 28 10:23:44 2014 UTC
@@ -13,6 +13,7 @@
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.Uninterruptibles;
 import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.haikuos.haikudepotserver.api1.model.user.*;
 import org.haikuos.haikudepotserver.api1.support.*;
@@ -249,29 +250,59 @@
public AuthenticateUserResult authenticateUser(AuthenticateUserRequest authenticateUserRequest) {
         Preconditions.checkNotNull(authenticateUserRequest);
AuthenticateUserResult authenticateUserResult = new AuthenticateUserResult();
-        authenticateUserResult.authenticated = false;
+        authenticateUserResult.token = null;
+        Optional<ObjectId> userOidOptional = Optional.absent();

-        if(null!=authenticateUserRequest.nickname) {
+        if (null != authenticateUserRequest.nickname) {
authenticateUserRequest.nickname = authenticateUserRequest.nickname.trim();
         }

-        if(null!=authenticateUserRequest.passwordClear) {
+        if (null != authenticateUserRequest.passwordClear) {
authenticateUserRequest.passwordClear = authenticateUserRequest.passwordClear.trim();
         }

- authenticateUserResult.authenticated = authenticationService.authenticate( + userOidOptional = authenticationService.authenticateByNicknameAndPassword(
                 authenticateUserRequest.nickname,
-                authenticateUserRequest.passwordClear).isPresent();
+                authenticateUserRequest.passwordClear);

-        // if the authentication has failed then best to sleep for a moment
-        // to make brute forcing a bit more tricky.
+        if(!userOidOptional.isPresent()) {

-        if(!authenticateUserResult.authenticated) {
+ // if the authentication has failed then best to sleep for a moment
+            // to make brute forcing a bit more tricky.
+            // TODO; this will eat threads; any other way to do it?
+
             Uninterruptibles.sleepUninterruptibly(5,TimeUnit.SECONDS);
         }
+        else {
+            ObjectContext context = serverRuntime.getContext();
+            User user = User.getByObjectId(context, userOidOptional.get());
+ authenticateUserResult.token = authenticationService.generateToken(user);
+        }

         return authenticateUserResult;
     }
+
+    @Override
+ public RenewTokenResult renewToken(RenewTokenRequest renewTokenRequest) {
+        Preconditions.checkNotNull(renewTokenRequest);
+ Preconditions.checkState(!Strings.isNullOrEmpty(renewTokenRequest.token));
+        RenewTokenResult result = new RenewTokenResult();
+
+ Optional<ObjectId> userOidOptional = authenticationService.authenticateByToken(renewTokenRequest.token);
+
+        if(userOidOptional.isPresent()) {
+            ObjectContext context = serverRuntime.getContext();
+            User user = User.getByObjectId(context, userOidOptional.get());
+            result.token = authenticationService.generateToken(user);
+            logger.info("did renew token for user; {}", user.toString());
+        }
+        else {
+            logger.info("unable to renew token");
+        }
+
+        return result;
+    }
+

     @Override
     public ChangePasswordResult changePassword(
@@ -317,7 +348,7 @@
throw new IllegalStateException("the old password clear is required to change the password of a user unless the logged in user is root.");
             }

-            if(!authenticationService.authenticate(
+            if(!authenticationService.authenticateByNicknameAndPassword(
                     changePasswordRequest.nickname,
                     changePasswordRequest.oldPasswordClear).isPresent()) {

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/User.java Mon Jun 2 11:04:38 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/User.java Sat Jun 28 10:23:44 2014 UTC
@@ -6,10 +6,13 @@
 package org.haikuos.haikudepotserver.dataobjects;

 import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.Iterables;
 import com.google.common.hash.Hashing;
 import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.query.ObjectIdQuery;
 import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.validation.BeanValidationFailure;
 import org.apache.cayenne.validation.ValidationResult;
@@ -26,6 +29,30 @@
public final static Pattern PASSWORDHASH_PATTERN = Pattern.compile("^[a-f0-9]{64}$"); public final static Pattern PASSWORDSALT_PATTERN = Pattern.compile("^[a-f0-9]{64}$");

+ public static User getByObjectId(ObjectContext context, ObjectId objectId) {
+        Preconditions.checkNotNull(context);
+        Preconditions.checkNotNull(objectId);
+ Preconditions.checkState(objectId.getEntityName().equals(User.class.getSimpleName()));
+
+        ObjectIdQuery objectIdQuery = new ObjectIdQuery(
+                objectId,
+                false, // fetching data rows
+                ObjectIdQuery.CACHE_NOREFRESH);
+
+        List result = context.performQuery(objectIdQuery);
+
+        switch(result.size()) {
+            case 0:
+ throw new IllegalStateException("unable to find the user from the objectid; " + objectId.toString());
+
+            case 1:
+                return (User) result.get(0);
+
+            default:
+ throw new IllegalStateException("more than one user returned from an objectid lookup");
+        }
+    }
+
public static Optional<User> getByNickname(ObjectContext context, String nickname) {
         return Optional.fromNullable(Iterables.getOnlyElement(
                 (List<User>) context.performQuery(new SelectQuery(
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AbstractUserAuthenticationAware.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AbstractUserAuthenticationAware.java Sat Jun 28 10:23:44 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -9,12 +9,9 @@
 import com.google.common.base.Preconditions;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.ObjectId;
-import org.apache.cayenne.query.ObjectIdQuery;
import org.haikuos.haikudepotserver.api1.support.AuthorizationFailureException;
 import org.haikuos.haikudepotserver.dataobjects.User;

-import java.util.List;
-
 /**
* <p>This class is the superclass for objects that are involved in the request response cycle that would like to * obtain the currently authenticated user. The currently authenticated user would have been authenticated using
@@ -49,30 +46,7 @@
Optional<ObjectId> authenticatedUserObjectId = AuthenticationHelper.getAuthenticatedUserObjectId();

         if(authenticatedUserObjectId.isPresent()) {
-
-            ObjectId objectId = authenticatedUserObjectId.get();
-
- if(!objectId.getEntityName().equals(User.class.getSimpleName())) { - throw new IllegalStateException("the object-id for the user authentication must be a user");
-            }
-
-            ObjectIdQuery objectIdQuery = new ObjectIdQuery(
-                    objectId,
-                    false, // fetching data rows
-                    ObjectIdQuery.CACHE_NOREFRESH);
-
-            List result = objectContext.performQuery(objectIdQuery);
-
-            switch(result.size()) {
-                case 0:
-                    break;
-
-                case 1:
-                    return Optional.of((User) result.get(0));
-
-                default:
- throw new IllegalStateException("more than one user returned from an objectid lookup");
-            }
+ return Optional.of(User.getByObjectId(objectContext, authenticatedUserObjectId.get()));
         }

         return Optional.absent();
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthenticationFilter.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthenticationFilter.java Sat Jun 28 10:23:44 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -21,6 +21,8 @@
 import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
 import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;

 /**
* <p>This authentication filter will catch authentication data on in-bound HTTP requests and will delegate the
@@ -38,6 +40,8 @@

protected static Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);

+ private static Pattern PATTERN_AUTHORIZATION_HEADER = Pattern.compile("^([A-Za-z0-9]+)\\s+(.+)$");
+
     @Resource
     AuthenticationService authenticationService;

@@ -53,25 +57,41 @@
         Optional<ObjectId> authenticatedUserObjectId = Optional.absent();

         if(!Strings.isNullOrEmpty(authorizationHeader)) {
-            if(authorizationHeader.startsWith("Basic ")) {
- byte[] usernamePasswordBytes = Base64.decode(authorizationHeader.substring(6));

- if(null!=usernamePasswordBytes && usernamePasswordBytes.length >= 3) { - List<String> parts = Lists.newArrayList(Splitter.on(":").split(new String(usernamePasswordBytes, Charsets.UTF_8))); + Matcher authorizationMatcher = PATTERN_AUTHORIZATION_HEADER.matcher(authorizationHeader);

-                    if(2==parts.size()) {
- authenticatedUserObjectId = authenticationService.authenticate(parts.get(0), parts.get(1));
-                    }
-                    else {
- logger.warn("attempt to process an authorization header, but the username password is malformed; is not <username>:<password>");
-                    }
-                }
-                else {
- logger.warn("attempt to process an authorization header, but the username password is malformed; being decoded from base64");
+            if(authorizationMatcher.matches()) {
+
+                switch(authorizationMatcher.group(1)) {
+
+                    case "Basic":
+ byte[] usernamePasswordBytes = Base64.decode(authorizationMatcher.group(2));
+
+ if (null != usernamePasswordBytes && usernamePasswordBytes.length >= 3) { + List<String> parts = Lists.newArrayList(Splitter.on(":").split(new String(usernamePasswordBytes, Charsets.UTF_8)));
+
+                            if (2 == parts.size()) {
+ authenticatedUserObjectId = authenticationService.authenticateByNicknameAndPassword(parts.get(0), parts.get(1));
+                            } else {
+ logger.warn("attempt to process an authorization header, but the username password is malformed; is not <username>:<password>");
+                            }
+                        } else {
+ logger.warn("attempt to process an authorization header, but the username password is malformed; being decoded from base64");
+                        }
+                        break;
+
+                    case "Bearer":
+ authenticatedUserObjectId = authenticationService.authenticateByToken(authorizationMatcher.group(2));
+                        break;
+
+                    default:
+ logger.warn("attempt to process an authorization header, but the authorization method {} is unknown :. ignoring", authorizationMatcher.group(1));
+                        break;
+
                 }
             }
             else {
- logger.warn("attempt to process an authorization header, but it is not basic authentication :. ignoring"); + logger.warn("attempt to process an authorization header, but it is malformed :. ignoring");
             }
         }

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthenticationService.java Thu Jan 16 08:37:44 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthenticationService.java Sat Jun 28 10:23:44 2014 UTC
@@ -5,24 +5,37 @@

 package org.haikuos.haikudepotserver.security;

+import com.google.common.base.Charsets;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.hash.Hashing;
+import com.nimbusds.jose.*;
+import com.nimbusds.jose.crypto.MACSigner;
+import com.nimbusds.jose.crypto.MACVerifier;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.ReadOnlyJWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.query.ObjectIdQuery;
 import org.haikuos.haikudepotserver.dataobjects.User;
+import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;

+import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
+import java.text.ParseException;
 import java.util.List;
+import java.util.UUID;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;

 /**
* <p>This service is able to provide the ability to authenticate a user given their nickname and their clear-text
@@ -36,14 +49,64 @@

protected static Logger logger = LoggerFactory.getLogger(AuthenticationService.class);

+    private final static String SUFFIX_JSONWEBTOKEN_SUBJECT = "@hds";
+
+ private final static Pattern PATTERN_JSONWEBTOKEN_ISSUER = Pattern.compile("^[a-z0-9]+\\.hds$");
+
+    /**
+ * <p>This secret is used to sign a token containing username / password / time for token-based authentication.
+     * </p>
+     */
+
+    @Value("${authentication.jws.sharedkey:}")
+    String jsonWebTokenSharedKey;
+
+    @Value("${authentication.jws.expiryseconds:300}")
+    Integer jsonWebTokenExpirySeconds;
+
+    @Value("${authentication.jws.issuer}")
+    String jsonWebTokenIssuer;
+
     @Resource
     ServerRuntime serverRuntime;

-    Cache<String,ObjectId> userNicknameToObjectIdCache = CacheBuilder
+    private JWSSigner jsonWebTokenSigner = null;
+
+    private JWSVerifier jsonWebTokenVerifier = null;
+
+ private Cache<String,ObjectId> userNicknameToObjectIdCache = CacheBuilder
             .newBuilder()
             .maximumSize(256)
             .expireAfterAccess(1, TimeUnit.MINUTES)
             .build();
+
+    @PostConstruct
+    public void init() {
+
+        if(jsonWebTokenExpirySeconds <= 15) {
+ throw new IllegalStateException("the expiry seconds for the json web token is too small");
+        }
+
+ if(Strings.isNullOrEmpty(jsonWebTokenIssuer) | | !PATTERN_JSONWEBTOKEN_ISSUER.matcher(jsonWebTokenIssuer).matches()) { + throw new IllegalStateException("the json web token issuer is malformed");
+        }
+
+        if(null!=jsonWebTokenSharedKey) {
+            jsonWebTokenSharedKey = jsonWebTokenSharedKey.trim();
+
+            if(jsonWebTokenSharedKey.length() < 10) {
+ throw new IllegalStateException("the json web token shared key length is too small to be secure");
+            }
+        }
+
+        if(Strings.isNullOrEmpty(jsonWebTokenSharedKey)) {
+            jsonWebTokenSharedKey = UUID.randomUUID().toString();
+ logger.warn("a shared key is not supplied so a random one has been created");
+        }
+
+ jsonWebTokenSigner = new MACSigner(jsonWebTokenSharedKey.getBytes(Charsets.UTF_8)); + jsonWebTokenVerifier = new MACVerifier(jsonWebTokenSharedKey.getBytes(Charsets.UTF_8));
+    }

     public ServerRuntime getServerRuntime() {
         return serverRuntime;
@@ -88,15 +151,15 @@
         }
     }

- public Optional<ObjectId> authenticate(String username, String passwordClear) { + public Optional<ObjectId> authenticateByNicknameAndPassword(String nickname, String passwordClear) {

         Optional<ObjectId> result = Optional.absent();

- if(!Strings.isNullOrEmpty(username) && !Strings.isNullOrEmpty(passwordClear)) { + if(!Strings.isNullOrEmpty(nickname) && !Strings.isNullOrEmpty(passwordClear)) {

             ObjectContext objectContext = serverRuntime.getContext();

- Optional<User> userOptional = getUserByNickname(objectContext, username); + Optional<User> userOptional = getUserByNickname(objectContext, nickname);

             if(userOptional.isPresent()) {
                 User user = userOptional.get();
@@ -106,11 +169,11 @@
result = Optional.fromNullable(userOptional.get().getObjectId());
                 }
                 else {
- logger.info("the authentication for the user; {} failed", username); + logger.info("the authentication for the user; {} failed", nickname);
                 }
             }
             else {
-                logger.info("unable to find the user; {}", username);
+                logger.info("unable to find the user; {}", nickname);
             }
         }
         else {
@@ -171,5 +234,135 @@
     private interface CharToBooleanFunction {
         boolean test(char c);
     }
+
+    // ---------------------------
+    // JSON WEB TOKEN
+
+    public Optional<ObjectId> authenticateByToken(String payload) {
+        Optional<SignedJWT> signedJwtOptional = verifyToken(payload);
+
+        if(signedJwtOptional.isPresent()) {
+            return authenticate(signedJwtOptional.get());
+        }
+
+        return Optional.absent();
+    }
+
+    /**
+ * <p>This method will validate the json web token and assuming that everything is OK, it will return
+     * an ObjectId that refers to the </p>
+     */
+
+    public Optional<ObjectId> authenticate(SignedJWT signedJwt) {
+        Preconditions.checkNotNull(signedJwt);
+        ReadOnlyJWTClaimsSet claimsSet;
+        long nowMillis = System.currentTimeMillis();
+
+        try {
+            claimsSet = signedJwt.getJWTClaimsSet();
+        }
+        catch(ParseException pe) {
+            throw new IllegalStateException("unable to parse the jwt",pe);
+        }
+
+        String issuer = claimsSet.getIssuer();
+
+        if(null==issuer||!issuer.equals(jsonWebTokenIssuer)) {
+ logger.info("rejected jwt authentication; the issuer '{}' on the jwt does not match the expected '{}'", issuer, jsonWebTokenIssuer);
+        }
+        else {
+            java.util.Date issueTime = claimsSet.getIssueTime();
+            java.util.Date expirationTime = claimsSet.getExpirationTime();
+
+            if(
+                    null==issueTime
+                            || null==expirationTime
+                            || nowMillis < issueTime.getTime()
+                            || nowMillis > expirationTime.getTime()) {
+ logger.info("rejected jwt authentication; the issue time or expiration time are invalid or do not contain the current time");
+            }
+            else {
+                String subject = claimsSet.getSubject();
+
+                if(
+                        null==subject
+ | | !subject.endsWith(SUFFIX_JSONWEBTOKEN_SUBJECT) + || subject.length() <= SUFFIX_JSONWEBTOKEN_SUBJECT.length()) { + logger.info("rejected jwt authentication; bad subject");
+                }
+                else {
+
+ String nickname = subject.substring(0,subject.length() - SUFFIX_JSONWEBTOKEN_SUBJECT.length());
+                    ObjectContext context = serverRuntime.getContext();
+ Optional<User> userOptional = getUserByNickname(context, nickname);
+
+                    if(userOptional.isPresent()) {
+ return Optional.of(userOptional.get().getObjectId());
+                    }
+                }
+            }
+        }
+
+        return Optional.absent();
+    }
+
+    /**
+ * <p>This method will take a JWT payload and will check the signing. If the signing and format + * of the payload is OK then it will return the payload parsed; otherwise it will return
+     * an absent.</p>
+     */
+
+    public Optional<SignedJWT> verifyToken(String payload) {
+        if(null!=payload && 0!=payload.length()) {
+            try {
+                SignedJWT signedJWT = SignedJWT.parse(payload);
+
+                try {
+                    if(signedJWT.verify(jsonWebTokenVerifier)) {
+                        return Optional.of(signedJWT);
+                    }
+                    else {
+ logger.error("attempt to use jwt that was unable to be verified");
+                    }
+                }
+                catch(JOSEException je) {
+ throw new IllegalStateException("unable to verify the jwt", je);
+                }
+            }
+            catch(ParseException pe) {
+ logger.error("rejected malformed jwt that was unable to be parsed",pe);
+            }
+        }
+
+        return Optional.absent();
+    }
+
+    /**
+ * <p>This will return a JWT (java web token) that is signed by a secret that allows for the client to get + * that token and for it to be used as a form of authentication for some period of time.</p>
+     */
+
+    public String generateToken(User user) {
+        Preconditions.checkNotNull(user);
+        DateTime now = new DateTime();
+
+        JWTClaimsSet claimsSet = new JWTClaimsSet();
+ claimsSet.setSubject(user.getNickname() + SUFFIX_JSONWEBTOKEN_SUBJECT);
+        claimsSet.setIssueTime(now.toDate());
+ claimsSet.setExpirationTime(now.plusSeconds(jsonWebTokenExpirySeconds).toDate());
+        claimsSet.setIssuer(jsonWebTokenIssuer);
+
+ SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);
+
+        try {
+            signedJWT.sign(jsonWebTokenSigner);
+        }
+        catch(JOSEException je) {
+            throw new IllegalStateException("unable to sign a jwt",je);
+        }
+
+        return signedJWT.serialize();
+    }
+

 }
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Mon Jun 23 10:01:05 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sat Jun 28 10:23:44 2014 UTC
@@ -72,7 +72,6 @@

<value>/js/app/controller/viewpkgcontroller.js</value> <value>/js/app/controller/homecontroller.js</value> - <value>/js/app/controller/errorcontroller.js</value> <value>/js/app/controller/createusercontroller.js</value> <value>/js/app/controller/viewusercontroller.js</value> <value>/js/app/controller/authenticateusercontroller.js</value>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/WEB-INF/web.xml Thu Dec 5 09:23:22 2013 UTC +++ /haikudepotserver-webapp/src/main/webapp/WEB-INF/web.xml Sat Jun 28 10:23:44 2014 UTC
@@ -39,9 +39,19 @@
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
     </filter>

+    <filter>
+        <filter-name>errorFilter</filter-name>
+ <filter-class>org.haikuos.haikudepotserver.support.web.ErrorFilter</filter-class>
+    </filter>
+
     <filter-mapping>
         <filter-name>authenticationFilter</filter-name>
         <url-pattern>/*</url-pattern>
     </filter-mapping>

+    <filter-mapping>
+        <filter-name>errorFilter</filter-name>
+        <url-pattern>/error</url-pattern>
+    </filter-mapping>
+
 </web-app>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateusercontroller.js Thu Jun 19 10:17:07 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateusercontroller.js Sat Jun 28 10:23:44 2014 UTC
@@ -71,11 +71,11 @@
                     }]
                 ).then(
                     function(result) {
-                        if(result.authenticated) {
+                        if(result.token && result.token.length) {

                             userState.user({
nickname : $scope.authenticationDetails.nickname, - passwordClear : $scope.authenticationDetails.passwordClear
+                                token : result.token
                             });

$log.info('successful authentication; '+$scope.authenticationDetails.nickname);
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Mon Jun 23 10:01:05 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Sat Jun 28 10:23:44 2014 UTC
@@ -33,7 +33,6 @@
.when(pkgVersionPrefix+'/editcategories',{controller:'EditPkgCategoriesController', templateUrl:'/js/app/controller/editpkgcategories.html'}) .when(pkgVersionPrefix+'/editversionlocalizations',{controller:'EditPkgVersionLocalizationController', templateUrl:'/js/app/controller/editpkgversionlocalization.html'}) .when(pkgVersionPrefix+'/adduserrating',{controller:'AddEditUserRatingController', templateUrl:'/js/app/controller/addedituserrating.html'}) - .when('/error',{controller:'ErrorController', templateUrl:'/js/app/controller/error.html'})
                 
.when('/',{controller:'HomeController',templateUrl:'/js/app/controller/home.html',reloadOnSearch:false})
.otherwise({controller:'OtherwiseController', template: '<div></div>'});
         }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Thu Apr 3 10:08:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Sat Jun 28 10:23:44 2014 UTC
@@ -11,15 +11,20 @@
     [ '$log','$location','breadcrumbs',
         function($log,$location,breadcrumbs) {

-            function navigateToError() {
+            /**
+ * <p>This function will exit the AngularJS environment into vanilla HTML.</p>
+             */
+
+            function navigateToError(code) {
                 breadcrumbs.reset();
-                $location.path('/error').search({});
+                var query = code ? '?jrpcerrorcd=' + code : '';
+                window.location.href = '/error' + query;
             }

             var ErrorHandlingService = {

-                navigateToError : function() {
-                    navigateToError();
+                navigateToError : function(code) {
+                    navigateToError(code);
                 },

                 logJsonRpcError : function(jsonRpcErrorEnvelope, message) {
@@ -42,7 +47,8 @@

                 handleJsonRpcError : function(jsonRpcErrorEnvelope) {
ErrorHandlingService.logJsonRpcError(jsonRpcErrorEnvelope);
-                    navigateToError();
+ var code = jsonRpcErrorEnvelope ? jsonRpcErrorEnvelope.code : undefined;
+                    navigateToError(code);
                 },

                 /**
@@ -103,7 +109,7 @@
                     var breadcrumbs = $injector.get('breadcrumbs');
                     $delegate(exception,cause);
                     breadcrumbs.reset();
-                    $location.path("/error").search({});
+                    window.location.href = '/error';
                 }
             }
         ]);
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Sat May 24 11:47:09 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Sat Jun 28 10:23:44 2014 UTC
@@ -13,17 +13,154 @@

 angular.module('haikudepotserver').factory('userState',
     [
-        '$log','$q','$rootScope','jsonRpc',
-        'pkgIcon','pkgScreenshot','errorHandling','constants','referenceData',
+        '$log','$q','$rootScope','$timeout','$window',
+        'jsonRpc','pkgScreenshot','errorHandling',
+        'constants','referenceData',
         function(
-            $log,$q,$rootScope,jsonRpc,
-            pkgIcon,pkgScreenshot,errorHandling,constants,referenceData) {
+            $log,$q,$rootScope,$timeout,$window,
+            jsonRpc,pkgScreenshot,errorHandling,
+            constants,referenceData) {

             const SIZE_CHECKED_PERMISSION_CACHE = 25;

-            var userStateData = {};
-            userStateData.naturalLanguageCode = 'en';
-            userStateData.user = undefined;
+            var tokenRenewalTimeoutPromise = undefined;
+
+            var userStateData = {
+                naturalLanguageCode : 'en',
+                user : undefined
+            };
+
+            // ------------------------------
+            // FOREGROUND / BACKGROUND JWT UPDATING
+ // The JWT will eventually expire. This set of functions is for avoiding that in the case + // by always fetching a new one just before the old one expires.
+
+            function setToken(token) {
+
+                if(null==token) {
+                    if(userStateData.user) {
+                        userStateData.user.token = undefined;
+                    }
+
+                    // remove the Authorization header for HTTP transport
+                    jsonRpc.setHeader('Authorization');
+                    pkgScreenshot.setHeader('Authorization');
+                }
+                else {
+ if (userStateData.user && userStateData.user.nickname == tokenNickname(token)) {
+                        userStateData.user.token = token;
+
+                        var authenticationContent = 'Bearer ' + token;
+
+ jsonRpc.setHeader('Authorization', authenticationContent); + pkgScreenshot.setHeader('Authorization', authenticationContent);
+                    }
+                    else {
+ $log.info('cannot set the token because the user state is not compatible with the token');
+                    }
+                }
+
+            }
+
+            function cancelTokenRenewalTimeout() {
+                if(tokenRenewalTimeoutPromise) {
+                    $timeout.cancel(tokenRenewalTimeoutPromise);
+                    tokenRenewalTimeoutPromise = undefined;
+                }
+            }
+
+            function configureTokenRenewal() {
+                if(userStateData.user && userStateData.user.token) {
+                    function millisUntilExpiration() {
+                        var nowMs = new Date().getTime();
+ var expMs = tokenExpirationDate(userStateData.user.token).getTime();
+                        return expMs - nowMs;
+                    }
+
+ var millisUntilRenewal = _.max([0,millisUntilExpiration() * 0.75]);
+
+                    tokenRenewalTimeoutPromise = $timeout(function () {
+                            if(millisUntilExpiration() < 0) {
+ $log.info('am going to renew token, but it has already expired'); + errorHandling.navigateToError(jsonRpc.errorCodes.AUTHORIZATIONFAILURE); // simulates this happening
+                            }
+                            else {
+                                jsonRpc.call(
+                                    constants.ENDPOINT_API_V1_USER,
+                                    'renewToken',
+                                    [
+                                        { token: userStateData.user.token }
+                                    ]
+                                ).then(
+                                    function (renewTokenResponse) {
+                                        if(renewTokenResponse.token) {
+ setToken(renewTokenResponse.token); + $log.info('did renew the authentication token');
+                                            configureTokenRenewal();
+                                        }
+                                        else {
+ $log.info('was not able to renew authentication token'); + errorHandling.navigateToError(jsonRpc.errorCodes.AUTHORIZATIONFAILURE); // simulates this happening
+                                        }
+                                    },
+                                    function (err) {
+ $log.info('failure to renew the authentication token'); + errorHandling.handleJsonRpcError(err);
+                                    }
+                                );
+                            }
+                        },
+                        millisUntilRenewal
+                    );
+                }
+            }
+
+            function tokenClaimSet(token) {
+                if(!token||!token.length) {
+                    throw 'missing json web token';
+                }
+
+                var parts = token.split('.');
+
+                if(3 != parts.length) {
+ throw 'json web token should contain three dot-separated parts';
+                }
+
+                return angular.fromJson(window.atob(parts[1]));
+            }
+
+            /**
+ * <p>If a user is authenticated with the system then this will return a non-null value that + * represents the date at which the authentication will expire.</p>
+             */
+
+            function tokenExpirationDate(token) {
+                var claimSet = tokenClaimSet(token);
+
+ if(!claimSet || !claimSet.exp | | !angular.isNumber(claimSet.exp)) { + throw 'malformed claim set; unable to get the \'exp\' data';
+                }
+
+                return new Date(claimSet.exp * 1000);
+            }
+
+            function tokenNickname(token) {
+                var claimSet = tokenClaimSet(token);
+
+                if(!claimSet || !claimSet.sub) {
+ throw 'malformed claim set; unable to get the \'sub\' data';
+                }
+
+                var sub = '' + claimSet.sub;
+                var suffixIndex = sub.indexOf('@hds');
+
+                if(-1==suffixIndex) {
+                    throw 'malformed nickname in token; missing suffix';
+                }
+
+                return sub.substring(0,suffixIndex);
+            }
+

             // ------------------------------
             // USER
@@ -36,10 +173,8 @@

                 if(null==value) {
                     userStateData.user = undefined;
-
-                    // remove the Authorization header for HTTP transport
-                    jsonRpc.setHeader('Authorization');
-                    pkgScreenshot.setHeader('Authorization');
+                    setToken(null);
+                    cancelTokenRenewalTimeout();
                 }
                 else {

@@ -47,19 +182,13 @@
throw 'the nickname is required when setting a user';
                     }

-                    if(!value.passwordClear) {
- throw 'the password clear is required when setting a user';
+                    if(!value.token) {
+ throw 'the json web token is required when setting a user';
                     }

- var basic = 'Basic '+window.btoa(''+value.nickname+':'+value.passwordClear);
-
-                    jsonRpc.setHeader('Authorization',basic);
-                    pkgScreenshot.setHeader('Authorization',basic);
-
-                    userStateData.user = {
-                        nickname : value.nickname,
-                        passwordClear : value.passwordClear
-                    };
+                    userStateData.user = { nickname : value.nickname };
+                    setToken(value.token);
+                    configureTokenRenewal();

$log.info('have set user; '+userStateData.user.nickname);
                 }
@@ -300,7 +429,7 @@
                 /**
* <p>Invoked with no argument, this function will return the user. If it is supplied with null then * it will set the current user to empty. If it is supplied with a user value, it will configure the - * user. The user should consist of the 'nickname' and the 'passwordClear'.</p> + * user. The user should consist of the 'nickname' and the 'token'.</p>
                  */

                 user : function(value) {
=======================================
--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/AbstractIntegrationTest.java Wed Jun 4 11:36:29 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/AbstractIntegrationTest.java Sat Jun 28 10:23:44 2014 UTC
@@ -9,12 +9,14 @@
 import com.google.common.base.Preconditions;
 import com.google.common.io.ByteStreams;
 import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.haikuos.haikudepotserver.dataobjects.User;
 import org.haikuos.haikudepotserver.security.AuthenticationHelper;
 import org.haikuos.haikudepotserver.security.AuthenticationService;
 import org.haikuos.haikudepotserver.support.Closeables;
 import org.haikuos.haikudepotserver.support.db.migration.ManagedDatabase;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.slf4j.Logger;
@@ -102,6 +104,11 @@
             Closeables.closeQuietly(preparedStatement);
         }
     }
+
+    @After
+    public void afterEachTest() {
+        setUnauthenticated();
+    }

     /**
* <p>Before each test is run, we want to remove all of the database objects and then re-populate
@@ -183,5 +190,9 @@
     protected void setAuthenticatedUserToRoot() {
         setAuthenticatedUser("root");
     }
+
+    protected void setUnauthenticated() {
+ AuthenticationHelper.setAuthenticatedUserObjectId(Optional.<ObjectId>absent());
+    }

 }
=======================================
--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/api1/UserApiIT.java Mon Jun 23 10:01:05 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/api1/UserApiIT.java Sat Jun 28 10:23:44 2014 UTC
@@ -7,7 +7,9 @@

 import com.google.common.base.Optional;
 import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
 import org.fest.assertions.Assertions;
+import org.haikuos.haikudepotserver.AbstractIntegrationTest;
 import org.haikuos.haikudepotserver.api1.model.user.*;
 import org.haikuos.haikudepotserver.api1.support.AbstractSearchRequest;
 import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
@@ -15,8 +17,6 @@
 import org.haikuos.haikudepotserver.captcha.model.Captcha;
 import org.haikuos.haikudepotserver.dataobjects.NaturalLanguage;
 import org.haikuos.haikudepotserver.dataobjects.User;
-import org.haikuos.haikudepotserver.security.AuthenticationService;
-import org.haikuos.haikudepotserver.AbstractIntegrationTest;
 import org.junit.Test;

 import javax.annotation.Resource;
@@ -80,7 +80,7 @@
Assertions.assertThat(userOptional.get().getNickname()).isEqualTo("testuser"); Assertions.assertThat(userOptional.get().getNaturalLanguage().getCode()).isEqualTo("en");

- Assertions.assertThat(authenticationService.authenticate("testuser","Ue4nI92Rw").get()).isEqualTo(userOptional.get().getObjectId()); + Assertions.assertThat(authenticationService.authenticateByNicknameAndPassword("testuser", "Ue4nI92Rw").get()).isEqualTo(userOptional.get().getObjectId());
     }

     @Test
@@ -110,7 +110,8 @@
AuthenticateUserResult result = userApi.authenticateUser(new AuthenticateUserRequest("testuser","U7vqpsu6BB"));
         // ------------------------------------

-        Assertions.assertThat(result.authenticated).isTrue();
+        Assertions.assertThat(result.token).isNotNull();
+ Assertions.assertThat(authenticationService.authenticateByToken(result.token).isPresent()).isTrue();

     }

@@ -125,7 +126,34 @@
AuthenticateUserResult result = userApi.authenticateUser(new AuthenticateUserRequest("testuser","y63j20f22"));
         // ------------------------------------

-        Assertions.assertThat(result.authenticated).isFalse();
+        Assertions.assertThat(result.token).isNull();
+    }
+
+    @Test
+    public void testRenewToken() {
+
+        String token;
+        ObjectId userOid;
+
+        {
+            ObjectContext context = serverRuntime.getContext();
+ User user = integrationTestSupportService.createBasicUser(context, "testuser", "U7vqpsu6BB");
+            userOid = user.getObjectId();
+            token = authenticationService.generateToken(user);
+        }
+
+        RenewTokenRequest renewTokenRequest = new RenewTokenRequest();
+        renewTokenRequest.token = token;
+
+        // ------------------------------------
+        RenewTokenResult result = userApi.renewToken(renewTokenRequest);
+        // ------------------------------------
+
+        {
+ Optional<ObjectId> afterUserObjectId = authenticationService.authenticateByToken(result.token); + Assertions.assertThat(userOid).isEqualTo(afterUserObjectId.get());
+        }
+
     }

     @Test
@@ -137,7 +165,7 @@
         setAuthenticatedUser("testuser");

         // check that the password is correctly configured.
- Assertions.assertThat(authenticationService.authenticate("testuser","U7vqpsu6BB").get()).isEqualTo(user.getObjectId()); + Assertions.assertThat(authenticationService.authenticateByNicknameAndPassword("testuser", "U7vqpsu6BB").get()).isEqualTo(user.getObjectId());

         // now change it.
         ChangePasswordRequest request = new ChangePasswordRequest();
@@ -152,8 +180,8 @@
         // ------------------------------------

// now check that the old authentication no longer works and the new one does work - Assertions.assertThat(authenticationService.authenticate("testuser","U7vqpsu6BB").isPresent()).isFalse(); - Assertions.assertThat(authenticationService.authenticate("testuser","8R3nlp11gX").get()).isEqualTo(user.getObjectId()); + Assertions.assertThat(authenticationService.authenticateByNicknameAndPassword("testuser", "U7vqpsu6BB").isPresent()).isFalse(); + Assertions.assertThat(authenticationService.authenticateByNicknameAndPassword("testuser", "8R3nlp11gX").get()).isEqualTo(user.getObjectId());

     }

=======================================
--- /haikudepotserver-webapp/src/test/resources/local.properties Mon Jun 2 11:04:38 2014 UTC +++ /haikudepotserver-webapp/src/test/resources/local.properties Sat Jun 28 10:23:44 2014 UTC
@@ -6,7 +6,13 @@
 jdbc.url=jdbc:postgresql://localhost:5432/haikudepotserver_integrationtest
 jdbc.username=haikudepotserver_integrationtest
 jdbc.password=haikudepotserver_integrationtest
+
 flyway.migrate=true
+
 webresourcegroup.separated=true
+
 userrating.aggregation.pkg.versionsback=2
 userrating.aggregation.pkg.minratings=3
+
+authentication.jws.sharedkey=F62144BC-F9FB-4C4B-A3BC-1558A06ED36F
+authentication.jws.issuer=integrationtest.hds

==============================================================================
Revision: 18e4a2cea192
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jun 28 10:46:28 2014 UTC
Log:      + resolve small problem with logout function

http://code.google.com/p/haiku-depot-web-app/source/detail?r=18e4a2cea192

Modified:
/haikudepotserver-webapp/src/main/webapp/js/app/controller/viewusercontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/bannerdirective.js

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewusercontroller.js Mon Jun 23 10:01:05 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewusercontroller.js Sat Jun 28 10:46:28 2014 UTC
@@ -60,10 +60,13 @@
             $scope.goEdit = function() {
breadcrumbs.pushAndNavigate(breadcrumbs.createEditUser($scope.user));
             }
+
+            /**
+ * <p>This method will logout the user; it will take them to the entry point for the application + * and in doing so the page will be re-loaded and so their state will be removed.</p>
+             */

             $scope.goLogout = function() {
-                userState.user(null);
-                breadcrumbs.reset();
                 $window.location.href = '/';
             }

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/bannerdirective.js Mon Jun 23 10:01:05 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/bannerdirective.js Sat Jun 28 10:46:28 2014 UTC
@@ -180,11 +180,13 @@
                         $scope.showActions = false;
                     };

+                    /**
+ * <p>This method will logout the user; it will take them to the entry point for the application + * and in doing so the page will be re-loaded and so their state will be removed.</p>
+                     */
+
                     $scope.goLogout = function() {
-                        userState.user(null);
-                        breadcrumbs.reset();
                         $window.location.href='/';
-                        $scope.showActions = false;
                     };

                     // -----------------

Other related posts:

  • » [haiku-depot-web] [haiku-depot-web-app] 5 new revisions pushed by haiku.li...@xxxxxxxxx on 2014-06-28 10:53 GMT - haiku-depot-web-app