Tuesday, February 14, 2017

Secure images with angular.

Ever needed to deliver images securely? I had a usecase for delivering map tiles only if the user was authenticated - but I was using stateless authentication with Java Web Tokens (JWT). In order to use web tokens I need to place an Authentication header on each web request - easy enough. But that isnt possible to do when using a html img tag - it only allows for a url souce.

So you have two options - extend your JWT token reading service to allow it to read tokens from the url params object too (ie <img src='someurl?token=foo'> ) or instead of using the standard src attribute - use javascript to securely download the image data into a blob, and use the html5 blob data attribute for img tags.

This angular directive will do such a thing. I based my solution on this source code- except noting adding that I use an angular service "Authentication" that has a promise that will resolve only if there is valid non-expired authentication (and will refresh the token if expired).

So I only needed to modify the directive's core method to add my extra waitForAuthenticated() promise.

attrs.$observe('httpSrc', function (url) {
                    revokeObjectURL();

                    if (url && url.indexOf('data:') === 0) {
                        $scope.objectURL = url;
                    } else if (url) {
                        Authentication.waitForAuthenticated().then(function(ok){
                        $http.get(url, {
                                responseType: 'arraybuffer',
                                headers: {
                                    'accept': 'image/webp,image/*,*/*;q=0.8'
                                }
                            })
                            .then(function (response) {
                                var blob = new Blob(
                                    [response.data],
                                    {type: response.headers('Content-Type')}
                                );
                                $scope.objectURL = URL.createObjectURL(blob);
                            });
                        });
                    }
                });

The blue text is the core of the directive, pipeling the securely delivered image data into an HTML objectURL. You can attach headers easily using an injected interceptor to all $http requests.. This wont work on older browsers not supporting the objectURL standard.

A working combination of grails 3, geoDB & hibernate spatial.

Ever since the move to grails 3 - dealing with plugins (for me) has become a pain. Almost all the documentation out there is for the now legacy 2.X branches. Makes me consider just using basic servlets like jetty or tomcat embedded. Anyway!

Hibernate spatial is helpful ORM when working with the Java Topology Suite - and since hibernate 5 is now worked into the core project.

To setup your grails project to use hibernate spatial - and for grails to successfully select the type of the JTS Geometry derviatives when defined in a domain class - you'll need to make sure all your hibernate versions stack up right to avoid no class data found or no class def found, or java.lang.NoSuchMethodError OR cannot resolve name errrors - all symptoms of mismatch java versions and/or class/interface definitions.

in your build gradle file:
(I happen to be using the latest grails 3.2.6)

* Make sure you are using the hibernate5 grails plugin - atleast version 6.0.0
* Make sure the version of hibernate-core & hibernate-ehcache & hibernate-spatial matches - atleast version 5.1.0 - but crucially - not greater than 5.2.0 - 5.2.0+ is currently incompatible with grails.
* Make sure you are using geoDB version 0.7 (for local testing) - but not version 0.8 (incompatible from my testing)

* In your bootstrap, make sure to upgrade the h2 database to contain goeDB features:

 try {
            def sql = new Sql(dataSource)
            sql.executeUpdate('CREATE ALIAS InitGeoDB for "geodb.GeoDB.InitGeoDB"')
            sql.executeUpdate('CALL InitGeoDB()')
        } catch (java.sql.SQLException e) {
            log.debug('', e)
        }

And thats it. After changing the plugins, make sure to do a gradle clean, and you should be able to correctly map classes like:

import com.vividsolutions.jts.geom.Geometry
import com.vividsolutions.jts.geom.GeometryFactory

class Feature {

    UUID id

    double lat
    double lon

    Geometry geometry

    def beforeInsert( ) {
        if ( lat!=null && lon!=null && geometry == null) {
            geometry = (new GeometryFactory()).createPoint( new Coordinate(lon, lat ))
        }
    }

    static mapping = {
        id(generator: "uuid2", type: "uuid-binary") // H2 & Postgres
    }

}