Showing posts with label Grails3. Show all posts
Showing posts with label Grails3. Show all posts

Sunday, January 28, 2018

Add time-zone sense . . .

As today's applications run from the Cloud hardware and serve requests coming in from time-zones spread across the globe, date-time-zone handling becomes more sensitive than ever. Applications need to be designed and built with this sensible capability and need to have not just Date-Time sense but also added Zone sense.

Recently, I had to work on adding Timestamp and Zone capability to a Grails3 application. The initial implementation & business-logic around a couple of Date-Time sensitive fields was written just to track Date in MM/dd/yyyy format using java.util.Date. The new requirement was to capture date, timestamp and timezone. Sounds simple, but not really ;). It actually involved a bit of exploration on many fronts including: Java, Hibernate, Grails and PostgreSQL database.

Environment: Grails 3.2.3, Java 8, PostgreSQL on MacOS High Sierra 10.13.2

First Steps - Towards the right direction (Java 8 Date-Time classes)

Java 8 Date Time classes

I did some first-hand reading on Java 8 date-time classes from java.time package and an up-front research on the support for these classes in PostgreaSQL, Hibernate, Spring Boot and Grails.

Java 8 (at last) added fluent date-time-zone API classes that are immutable, thread-safe and based on ISO 8601 calendar. I looked into details of few classes like: Instant, LocalDateTime, ZonedDateTime and OffsetDateTime. From Java docs, OffsetDateTime is the class recommended for modeling date-time concepts in more detail and when communicating with database/networking. After experimenting little bit all these classes, without further exploration I chose java.time.OffsetDateTime class over java.util.Date.

java.time.Instant - an instantaneous point on the time line in UTC zone
e.g 2018-01-21T12:21:12.122Z indicating Jan 21, 2018 12:21 pm 12 seconds and 122 milliseconds, Z stands for UTC timezone)

java.time.LocalDateTime - date-time with no timezone information
e.g. 2018-01-21T07:21:12.122 indicating Sun Jan 21, 2018 07:21 am 12 seconds and 122 milliseconds with no timezone, as my system clock is set to EST, this is the time in EST America/New_York zone

java.time.ZonedDateTime - current date time in the current timezone with offset from UTC
e.g. 2018-01-21T07:21:12.122-05:00[America/New_York] indicating Sun Jan 21, 2018 07:21 am 12 seconds and 122 milliseconds with timezone offset and timezone. As my system clock is set to EST, this is the time in EST America/New_York zone. It's more like LocalDateTime with offset from UTC timezone but with local timezone)

java.time.OffsetDateTime - current date time in the current timezone with offset from UTC
e.g. 2018-01-21T07:21:12.122-05:00 indicating Sun Jan 21, 2018 07:21 am 12 seconds and 122 milliseconds with timezone offset. As my system clock is set to EST, this is the time in EST with -05:00 offset that indicates the zone. It's more like LocalDateTime with offset from UTC timezone. Offset indicates the zone.)

Other useful classes

Next Steps - Along the path (PostgreSQL)

PostgreSQL

It is quite common to leverage Grails Database Migration Plugin in a Grails application to manage database changes. When model changes, running the plugin provided dbm-gorm-diff command generates schema changes as a groovy script. For java.util.Date and java.time Date classes the plugin generated change-log script will have column types set to: TIMESTAMP WITHOUT TIME ZONE for PostgreSQL.

For instance, if a domain class had two new properties:
Agreement.groovy
class Agreement { ... Date signedDate Date expirationDate ... }

Then running dbm-gorm-diff
e.g. grails -Dgrails.env=development dbm-gorm-diff agreement-changes.groovy

would have change-log script generated for the model changes as follows:
databaseChangeLog = { changeSet(author: "Gpottepalem (generated)", id: "1234567890123-1") { createTable(tableName: "agreement") { ... column(name: "signed_date", type: "TIMESTAMP WITHOUT TIME ZONE") { constraints(nullable: "false") } column(name: "expiration_date", type: "TIMESTAMP WITHOUT TIME ZONE") { constraints(nullable: "false") } ... } } }

That clearly indicates NO time-zone details are stored in the database.

Move on - Application Code Changes (Grails)

Grails application code

Step-1 Dependencies - Add compile-time and run-time dependencies to gradle build script.

PostgreSQL jdbc driver
- Use the correct PostgreSQL JDBC driver for Java 8 date time classes support. Make sure you are using JRE 8+ driver. If not, change it:

Hibernate - Next add hibernate Java8 compile time dependency.

Grails - Then add grails Java8 dependency plugin

Following is the Gradle build script with these dependencies added:
build.gradle
dependencies { ... runtime "org.postgresql:postgresql:42.1.4" //Java JDBC 4.2 (JRE 8+) driver for PostgreSQL database compile 'org.hibernate:hibernate-java8 //hibernate java 8 java.time classes support compile "org.grails.plugins:grails-java8" //grails java 8 java.time classes support ... }

Step-2 Domain Model - Change Date types to OffsetDateTime

Agreement.groovy
class Agreement { ... OffsetDateTime signedDate OffsetDateTime expirationDate ... }

Assuming that the model had Date types earlier and the generated dbm change-log script: agreement-changes.groovy was already applied to database, now as the domain object properties are changed from java.util.Date to java.time.OffsetDateTime, we need to generate change-log script for these changes and apply them to database. But, the command dbm-gorm-diff will not generate the change-log script to have the right PostgreSQL type: TYPE TIMESTAMP WITH TIMEZONE. So, it requires a manual dbm change-log like:

e.g. agreement-date-type-changes.groovy
databaseChangeLog = { changeSet(author: 'gpottepalem', id: 'alter signed_date to store time zone') { grailsChange { change { sql.execute('ALTER TABLE myschema.my_table ALTER COLUMN signed_date TYPE TIMESTAMP WITH TIME ZONE') } } } changeSet(author: 'gpottepalem', id: 'alter expiration_date to store time zone') { grailsChange { change { sql.execute('ALTER TABLE myschema.my_table ALTER COLUMN expiration_date TYPE TIMESTAMP WITH TIME ZONE') } } } }

With this schema change applied to database, when the domain object is saved, OffsetDate gets stored in PostgreSQL database with timestamp and offset indicating the time zone.

Step-3 Views - JSON views

If you have JSON views in the application, you need to make sure that the OffsetDateTime fields' data looks good and is as expected in the JSON output produced. Unlike java.util.Date fields, java.time.OffsetDateTime fields output data won't get formatted. The JSON output for OffsetDateTime fields not only contain all of it's direct properties but also recursive properties of all of it's object references accessible via getXXX() methods. e.g. OffsetDateTime's getOffset() method's returned ZoneOffset object's getAvailableZoneIds() method's returned all zoneIds will show up as well.

For instance, if you have a view under grails-app/views/agreement/_agreement.gson like:
model { Agreement agreement } json.agreement { signedOn agreement.signedDate expiresOn agreement.expirationDate }

You will have it rendered in JSON as:
{ "agreement": { "signedOn": { "dayOfMonth": 27, "dayOfWeek": "SATURDAY", "dayOfYear": 27, "hour": 12, "minute": 44, "month": "JANUARY", "monthValue": 1, "nano": 356000000, "offset": { "availableZoneIds": [ "Asia/Aden", "America/Cuiaba", "Etc/GMT+9", ... ], "id": "-05:00", "rules": { "fixedOffset": true, "transitionRules": [], "transitions": [] }, "totalSeconds": -18000 }, "second": 35, "year": 2018 }, "expiresOn": { "dayOfMonth": 27, ... } } }

A quick solution is to simply convert java.time.OffsetDateTime to java.util.Date or simply stringify using toString() depending on your needs. The following gson template:
import java.time.Instant import java.time.OffsetDateTime model { Agreement agreement } json.agreement { signedOn agreement.signedDate.toString() //current zone date-time with offset from UTC expiresOn Date.from(agreement.expirationDate.toInstant()) //OffsetDateTime to Date in UTC timezone }
will produce the output as:
{ "agreement": { "signedOn": "2018-01-27T17:10:57.443-05:00", "expiresOn": "2018-01-27T22:10:57Z" } }

Gotcha

PostgreSQL stores offset when OffsetDateTime field is persisted. But when queried, will not give stored offset back. Instead, gives data in the timezone the database is set (for instance UTC) with no offset. Databases add their own steep learning curve to deal with date-time-zone data ;)

Tip

Grails console is the handy tool for explorations with changes like this in a Grails application.
e.g. A simple Grails script to persist and retrive Agreement object
import com.my.app.Agreement import java.time.OffsetDateTime println Agreement.count() Agreement agreement = new Agreement( signedDate: OffsetDateTime.now(), expirationDate: OffsetDateTime.now().plusYears(1) ).save(flush: true, failOnError: true) println Agreement.count() Agreement.all.each { println "id:${it.id}, signedDate:${it.signedDate} expirationDate:${it.expirationDate}" }

Summary

Date - is the central issue of Y2K problem which costed IT industry billions of dollars and isn't trivial yet. Even in modern languages and frameworks, it hasn't become as primitive as it can become, and dealing with it requires a special care and attention from all levels of the application right from presentation, business logic, and up to the persistence.

In modern Java applications, Java 8's added date.time classes have become better if not any simpler, but is a definitive way to go with, when dealing with date, time and zone in present times!

References


Sunday, December 03, 2017

G3 Summit 2017 - A Great Experience . . .

It was a great learning experience for me at this year's G3 Summit 2017 from Nov 28 - Dec 1, 2017 in Austin, Texas. After following Grails for many years and experiencing at work for about 2+ years, going to this conference definitely made me a better Grails Developer and I have several take-aways from this.

It was a 4-day conference, first two days were loaded with hands-on workshops followed by two full days of different one-and-half hour sessions in 3 parallel tracks on various Groovy, Grails and Gradle topics.

Day-1: Workshop

I attended Alexa Work Shop by Ryan Vanderwerf which was a good introduction to Amazon Alexa, it's Appkit, Architecture, SDKs, Skills, Intents Schema, Alexa Grails Plugin followed by a hands-on development of a simple Quiz application for Alexa and testing it using Alexa Skill Testing tool Echosim.

Day-2: Workshop

Second day I attended Powerful Testing Techniques for Groovy by Jeff Scott Brown. It was a good presentation on complete coverage of testing and it's importance with special focus on Groovy and Grails including the recent new Traits based Testing Framework introduced in Grails 3.3.x, followed by a hands-on session based on one of the Grails guides - Testing a Secured Grails Application. It was a very informative session overall.

Key Note: Opening Night

Paul King gave an update on Groovy and it's roadmap touching what's coming in 2.6 and 3.0 followed by Graeme Rocher on Grails and it's future giving brief insights into the already solid Grails 3.3, Gorm 6.1 and what's coming in Grails 4 with Gorm 7. Graeme also briefed on Grails new project called Particle which will aim to solve complex problems in the micro-services world with an elegant Grails approach.

Day-3: Sessions I attended

Groovy the awesome parts - Paul King
In this Groovy session, Paul King touched almost all awesome language features of Groovy with code snippets and explanations including DSL, IDE DSLs for code completion:GDSL(IntelliJ) & DSLD(Eclipse), AST transformations, Traits, Closures, GPars, Data Flows, @TypeChecked, @CompileStatic, Run-time meta-programming, Command Chains etc.

It was a good session to know awesome parts of Groovy and how Groovy makes them possible.

Monitoring & Metrics with Grails 3 - Jeff Brown
This talk was all about a work-in-progress but almost ready for ver 1.0 (currently at 1.0.0 M3), a  metrics Plugin for Grails called  Grails Dropwizard Metrics which is based on Dropwizard Java library.  He also did a demo of it's practical application and showed how simple and easy it is to collect metrics of a running application and automate any followup actions based on metrics collected by applying just a couple of annotations in the code: @Metered and @Timed.

It's definitely a plugin to check it out.

Programming Groovy with Java 8 - Venkat Subramaniam
As always Venkat came up with an awesome session by showing many small live coding examples of Groovy and Java primarily focusing on Collections and Streams with side-by-side comparisons. He demonstrated how one can take advantage of Laziness offered by Java 8 when working with collections through Streams and how well Java implemented it when many other programming languages including Groovy, Ruby, Python, JavaScript etc. only made the implementation elegant but didn't make it performant. He demonstrated clearly but concisely certain areas in the code while working with large sets of data where a Groovy Programmer can switch to Java 8 Streams to write performant code.

With Groovy's seamless integration with Java, of course, Groovy programmers are in the best spot to enjoy best of both, always, without leaving Groovy.

Grails Multi-project builds - Jeff Brown
It was another great session by Jeff Brown. He showed how easy it is to take advantage of Gralde's support for multi-project builds and get benefited by it in in a Grails project by organizing it into a root Grails project with sub-projects of several Grails plugins and libraries.

This seems clearly the way to start breaking down a monolithic giant complex Grails project into maintainable and dependable sub-projects without having any circular dependencies. He showed how Grails-core code-base itself is now organized into a multi-project Gradle build and this can be taken as a best example to look at.

GORM and GraphQL - James Kleeh
James Kleeh clearly started the session by making a point on how rigid REST is with end-points & their response schemas, and what GraphQL can offer to address this problem by giving the control to clients so that the client application can ask for what it needs and get back only what it asked for in the response. He also demonstrated a Grails GraphQL Demo app with GORM GraphQL plugin for Grails.

Go and check it out.

GORM 6 Q & A - Graeme Rocher & James Kleeh
It was a great Q & A session with a small group of people attended. I got a change to talk with Graeme and James and get to know more details on GORM 6.1, some insights into the changes went into GORM 6.1 and reasons behind those. In response to my question about Grails future project Particle,  Graeme described little more details on what Grails is aiming to do and what kind of problems it's trying to solve in there. The discussion went on sharing many more thoughts and insights around GORM and Grails framework- the past, the present and the future.

I am glad I attended this Q&A with Graeme and James.

Day-4: Sessions I attended

Polyglot Web Development with Grails 3 - Jeff Brown
This was another wonderful session by Jeff Brown who started this session with an introduction to Polyglot Programming and it's benefits followed by a demo. He demonstrated and showed how easy it is to leverage other JVM programming languages in a Grails application. He took Clojure -a pure functional programming language on the JVM as an example, and showed how one can write business logic in pure Clojure functions, how easy it is to expose those functions as service methods in a Controller which is injected with a Proxy service bean that uses Groovy's magical method: missingMethod implementation to call Clojure functions. With this approach, calling service methods in Controller is like any other Grails Service methods. The controller doesn't need to know in which language these services are implemented. He showed all pretty neat techniques in achieving this with the very simple Grails Clojure plugin that he wrote.

In Grails, polyglot programming can also be made possible as easy as it can be.

Automated Strategies for building & Deploying Grails with Gradle - Eric Helgeson
It was a pretty good session by Eric and he covered so many things in such a short one-and-half-hour fully informative session. He covered many available tools and things to do in order to achieve complete automation by doing everything as code right from taking the application code along the continuous integration path to production, and even beyond, all the way up to proactively responding to end-user issues.

There were great many ideas, tools and implementation details to take away from this session.

Grails 3, Gradle & AWS tour - Ryan Vanderwerf
This session was a quick introduction to Amazon's Virtual Private Cloud(VPC), Network terminology, and Amazon Services like Route 53, EC2, S3, Elastic Bean Stalk, Lambda and Elastic Load Balancer etc. with few quick demos of AWS JAVA SDK plugin and some coverage of Amazon's Simple Email Service (SES),Simple Queue Service (SQS) and Simple Notification Service (SNS) etc. It was good to know that there are many Gradle plugins for automating Amazon services already, and SSHOOGR- a Groovy DSL for SSH to work with remote services.

Getting Groovy with Google Home - Ryan Vanderwerf
This session was also by Ryan which was very interesting to know what Google is offering at present in the area of Voice Controlled Gadgets and Devices like Google Home and what is available in Grails world to start writing applications for Google Home device. With a quick introduction to Google Actions (equivalent to Alexa Skills), Intents and available tools like factions CLI, Google console etc., he dived into demonstrating a Color Chooser app which he had written in Grails using Actions SDK for Java. He demoed, how one can ask Google Home to tell the lighter or darker color of any color which sends the voice request to the Grails app deployed on Amazon AWS but setup on Google Cloud that delivers the response as an answer back to Google Home device. Ryan also had written an in-depth guide on this which is available at OCI web site.

Voice Controlled Devices is certainly the future way of interacting with applications & services, and a Grails Developer can already start writing apps in Grails for Google Home device. This was the very last session I attended and that concludes my experience with great G3 Summit 2017 - a Groovy, Grails and Gradle conference.

Summary

Overall, it was a great learning experience and I have several take-aways from this conference. Grails is a great community and this conference gave me an opportunity to meet many of the core committers of Groovy & Grails Framework, and other users & enthusiasts from Groovy, Grails and Gradle community.

Links

Tuesday, September 05, 2017

Create a Secured Restful API App with Grails 3 and PostgreSQL - Step by Step: Part 5 of 5

Part 5: Assure REST & Publish your API

At the end of my last post, we had a RESTful application with an end-point fully implemented and secured. However, we had not written any unit/integration test-specs. In this post we will write integration test-spec and mix it with REST Assured and Spring REST Docs to not only test the end-point but also to generate and publish API documents.

Importance of API Documentation

API is the middleware now. By following standard principles, the behavior of API can be consistent and predictable. Like any piece of code, APIs must be tested by all means from writing test-cases to assuring it's quality by QA. Ideally, when the application is assembled, API docs must also be generated and bundled into the artifact and be delivered together for deployment. That makes API docs a "living source of documents" and they become a common source of reference.

Spring REST Docs

There are few popular frameworks/tools to generate API documentation. Each one of such frameworks/tools has it's own adopted approach and comes with it's own benefits and drawbacks when compared with others. However, Spring IO has a project called Spring REST Docs that uniquely takes a very different approach. It's approach is centered around testing and is combined with hand-written Asciidoctor templates to produce high quality and maintainable API documentation. This approach definitely stands out as it promotes testing to it's greatest levels.

With the test-centric approach, it makes API document not only accurate-and-complete but also up-to-date and a living-resource reference. API documents generated this way are always as accurate as the code-base is. Also, as Spring framework is central to Grails framework, it becomes a natural-fit for Grails applications.

Having said all that, let's add Spring REST Docs to Grails 3 project and assure it with REST Assured.

Environment: Grails 3.2.11, Java 1.8, Apache Tomcat 8.0.20, IntelliJ IDEA Ultimate 2017.2 on Mac OS X 10.11.5 (El Capitan)

Step 0: Upgrade application from Grails 3.1.6 to 3.2.11

When I started this multi-part posts, Grails was at 3.1.6 and now it has advanced to 3.3.x. Just to catch up, I've upgraded this app from 3.1.6 to 3.2.11 (the latest on 3.2.x branch). It was an easy upgrade as it is a simple RESTful application. All I had to do was to bring gradle.properties and build.gradle files up-to-date with 3.2.11.

Step 1: Add Spring REST Docs to the Project (build configuration)

At the end of my last post, we had a secured resource (Artist) and we tested it's RESTful API for CRUD operations. That is good enough resource for taking it to the next level of generating & publishing it's API. Spring REST Docs' Getting Started has link to sample applications for reference. REST Assured Grails is the best-bet and is the basis for us. As a first step let's add Spring REST Docs support to the project as shown and described below:

Add Asciidoctor plugin.
build.gradle
plugins { ... id 'org.asciidoctor.convert' version '1.5.3' }

Run gradle tasks command and notice that asciidoctor task gets added by the plugin.
$./gradlew tasks ... Documentation tasks ------------------- asciidoctor - Converts AsciiDoc files and copies the output files and related resources to the build directory. groovydoc - Generates Groovydoc API documentation for the main source code. ... javadoc - Generates Javadoc API documentation for the main source code.

Spring REST Docs Build Configuration section has steps for Gradle build configuration. I will do this slightly different to extend build script for REST docs support by separating out additional build configuration for REST docs into it's own build file leveraging Gradle's script plugin concept. This way it is more cleaner and brings in some modularity to the build script.

Create a new build script file restdocs.gradle under project's gradle dir and reference it in the main build.gradle file at the very bottom as shown below:
build.gradle
apply from: 'gradle/restdocs.gradle'

Let's populate restdocs.gradle as shown below. I will add comments into the build script to explain certain code blocks.
gradle/restdocs.gradle
buildscript { repositories { maven { url 'https://repo.spring.io/libs-snapshot' } } } repositories { maven { url 'https://repo.spring.io/libs-snapshot' } } //add extra user-defined properties to the project through ext block ext { snippetsDir = file('build/docs/generated-snippets') //output dir of rest api doc snippets generated restDocsVersion = '2.0.0.BUILD-SNAPSHOT' restAssuredVersion = '2.9.0' } dependencies { testCompile "org.springframework.restdocs:spring-restdocs-core:$restDocsVersion" testCompile "org.springframework.restdocs:spring-restdocs-restassured:$restDocsVersion" testCompile "org.springframework.restdocs:spring-restdocs-asciidoctor:$restDocsVersion" }

Now, just run grails clean command. We will have spring-restdocs-core and spring-restdocs-restassured downloaded from maven central repo.

Let's keep expanding this script.
//task to clean generated rest api docs snippets dir task cleanSnippetsDir(type: Delete){ delete fileTree(dir: snippetsDir) }

Run ./gradlew tasks and notice that there is a new task added under Other tasks like:
Other tasks ----------- cleanIdeaWorkspace cleanSnippetsDir console

Configure test task as shown below:
test { dependsOn cleanSnippetsDir outputs.dir snippetsDir }

Now run ./gradlew test -m or ./gradew test --dry-run which will run gradle's test task in a dry run mode. It disables all tasks and shows the order in which tasks get executed. In this case, we can now see our new task cleanSnippetsDir in the list after all classes are created and before test-case classes get compiled.

Remember we got asciidoctor task by adding Gralde plugin as the very first step. We will customize it and specify that it depends on integrationTest task. With this dependency, every time when we run this task, it will have integration tests run. We want this kind of dependency as the approach that REST Docs brings in is to have REST API docs generated from the integration test cases. So, we need integration tests to run before we have docs generated.

Having said that, let's customize that task as follows:
//Configure asciidoctor task provided by Gradle asciidoctor plugin- https://github.com/asciidoctor/asciidoctor-gradle-plugin asciidoctor { doFirst{ //just print outputDir for reference during execution phase println "Running asccidoctor task. Check generated REST docs under: ${outputDir}" } dependsOn integrationTest logDocuments = true sourceDir = file('src/docs') inputs.dir snippetsDir separateOutputDirs = false attributes 'snippets': snippetsDir //configure snippets attribute for .adoc files }

Step 2: Run tests and make them pass: grails test-app

Grails test-app runs both unit tests and integration tests.
I have not written any test specifications so far but as part of creating domain objects using grails create-domain-class command, I have a few Spock Specification unit-tests created each with a default feature method "test something"(). All these default generated specifications are expected to fail to start with. I want to keep these tests around for future but want to make them pass. An easy way is to annotate all those methods with groovy's @NotYetImplemented annotation. It reverses the net result by making it pass when it actually fails. It makes sense for an un-implemented test. But when actually implemented, it fails forcing us to remove the annotation.

Spock's @PendingFeature is similar but is added only in Spock 1.1. Grails 3.2.x comes with Spock 1.0. For now, we are all good with that wonderful annotation provided by groovy. With this we have all unit-tests passing.

It's time now to write an integration test specification for our RESTful controller: ArtistController. Instead of writing a typical integration test-case, let's mix it with REST assured and REST API Docs and get both testing and API docs generation done in this phase.

Step 3: Assure REST by writing integration specification for RESTful Controller with a mix of REST Docs and a touch of REST assured.

Step 3a: Configure REST Assured testing framework (set up your test specification to generate documentation snippets)

The Spring REST Docs documentation has outlined these steps. Here is the gist of it:

The configuration of REST Assured is nothing but a request spec (RequestSpecifiction) using ResqusetSpecBuilder by adding documentation configuration as a JUnit filter to it.

Configure REST assured documentation output directory by declaring a restDocumentation field which is initialized with an instance of JUnitRestDocumentation and annotate it with JUnit's @Rule annotation. This rule gets executed before and after each feature method. A custom output directory can be specified by passing a constructor argument. We specify this custom dir, as in the build file, the snippetsDir property we set with is slightly different ('build/docs/generated-snippets') than the default ('build/generated-snippets').

Next, setup RequestSpecification by adding a filter and configure it with the above restDocumentation initialized as JUnit Rule.

Here is how our test spec looks after this configuration:
src/integration-test/groovy/com/giri/ApiDocumentationArtistSpec
package com.giri import geb.spock.GebSpec import grails.plugins.rest.client.RestBuilder import grails.test.mixin.integration.Integration import grails.transaction.Rollback import io.restassured.builder.RequestSpecBuilder import io.restassured.specification.RequestSpecification import org.junit.Rule import org.springframework.restdocs.JUnitRestDocumentation import static org.springframework.http.HttpStatus.* import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.documentationConfiguration @Integration @Rollback class ApiDocumentationArtistSpec extends GebSpec { @Rule protected JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation('build/docs/generated-snippets') private RequestSpecification documentationSpec def setup() { //set documentation specification this.documentationSpec = new RequestSpecBuilder().addFilter( documentationConfiguration(this.restDocumentation)) .build() } ...

Step 3b: Spockify, test RESTful end-point and get documentation snippets generated

With the above configuration, let's write a feature method to test GET request of /api/artists end-point. The following is a feature method added to the above specification along with a defined static constant whose value is set with relative end-point url and an injected application port property. The port is required to override the default port(8080) of REST assured testing framework. Note that grails start the application on a random available port each time when integration tests are run.

static final String ARTISTS_ENDPOINT = '/api/artists' @Value('${local.server.port}') protected int port ... void "test and document GET request (index action) of end-point: /api/artists"() { given: "" RequestSpecification requestSpecification = RestAssured.given(this.documentationSpec) .accept(MediaType.APPLICATION_JSON_VALUE) .filter( RestAssuredRestDocumentation.document( 'artists-list-example' ) ) when: def response = requestSpecification .when() .port(port) .get(ARTISTS_ENDPOINT) then: response.then() .assertThat() .statusCode(HttpStatus.OK.value()) }

The feature method name describes the intent of this feature method. In this step, we are only testing the GET request of an end-point. We will add the support for the highlighted and document intent of this feature.

With this, if you run grails dev test-app or grails -Dgrails.env=development test-app, the test will pass. Also, we will have the following six documentation snippets generated under build/docs/generated-snippets/artists-list-example directory:
 curl-request.adoc
 http-request.adoc
 http-response.adoc
 httpie-request.adoc
 request-body.adoc
 response-body.adoc

These are the snippet files to be included in the final API documentation. Just to see the contents check http-response.adoc and it will contain the actual response received as follows:
---- HTTP/1.1 200 OK X-Application-Context: application:development:0 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 29 Aug 2017 22:12:13 GMT Content-Length: 148 [{"id":"90ff9ac4-b1c0-4495-94d5-1550f463561a","dateCreated":"08/29/2017","firstName":"Giridhar","lastName":"Pottepalem","lastUpdated":"08/29/2017"}] ----

Step 3c: Create asciidoctor (.adoc) source templates
Create src/docs dir and create api-guide.adoc and artists.adoc files to start with. The api-guide.adoc is the main asciidoctor template which will include artists.adoc. The artists.adoc is the asciidoctor template for artists end-point.

Shown below is a portion of api-guide.adoc
= giri-api RESTful API Guide Giridhar Pottepalem :doctype: book :icons: font :source-highlighter: highlightjs :toc: left :toclevels: 4 :sectlinks: [[overview]] = Overview [[overview-http-verbs]] == HTTP Methods giri-api API follows standard HTTP and REST conventions as closely as possible in its exposure of resources as end-points and use of HTTP methods (verbs). ... [[resources]] = Resources include::artists.adoc[]

And portions of artists.adoc is shown below for creating an Artist (POST request/save action):
[[resources-artists]] == Artists An Artist is a resource which represents an Artist. [[resources-artists-create]] === Creating an Artist A `POST` request is used to create a new Artist. TIP: An Artist can be created only by an Admin user (with role `ROLE_ADMIN`) IMPORTANT: Once a new Artist is created... ==== Request structure include::{snippets}/artists-create-example/request-fields.adoc[] ==== Example request include::{snippets}/artists-create-example/curl-request.adoc[] ==== Response structure include::{snippets}/artists-create-example/response-fields.adoc[] ==== Example response include::{snippets}/artists-create-example/http-response.adoc[]
Highlighted are the references to generated snippets that get included in the generated end HTML5 doc.

Step 3d: Generate API doc
Now, lets run asciidoctor gradle task we got added through Step 1 as shown below:
./gradlew asciidoctor //runs in test env
./gradlew -Dgrails.env=development asciidoctor //runs in dev env

This task runs all integration test specifications because we configured it to depend on integrationTest task. Once it's run successfully with no failing tests, it converts our asciidoctor API templates to HTML5 doc by populating it with included generated snippets as we referenced in artists.adoc.

Now let's enhance our specification feature method to document request and response payload structure. Let's take the case of /api/artists end-point and GET request. There is no request payload for this request. So, we will simply add response payload specification as shown below:
void "Test and document show Artist request (GET request, show action) to end-point: /api/artists"() { given: "Pick an artist to show" Artist artist = Artist.first() and: "user logs in by a POST request to end-point: /api/login" String accessToken = authenticateUser('me', 'password') and: "documentation specification for showing an Artist" RequestSpecification requestSpecification = RestAssured.given(this.documentationSpec) .accept(MediaType.APPLICATION_JSON_VALUE) .filter( RestAssuredRestDocumentation.document( 'artists-retrieve-specific-example', PayloadDocumentation.responseFields( PayloadDocumentation.fieldWithPath('id').type(JsonFieldType.STRING).description('Artist id'), PayloadDocumentation.fieldWithPath('firstName').type(JsonFieldType.STRING).description('Artist first name'), PayloadDocumentation.fieldWithPath('lastName').type(JsonFieldType.STRING).description('Artist last name'), PayloadDocumentation.fieldWithPath('dateCreated').type(JsonFieldType.STRING).description("Date Created (format:MM/dd/yyyy)"), PayloadDocumentation.fieldWithPath('lastUpdated').type(JsonFieldType.STRING).description("Last Updated Date (format:MM/dd/yyyy)") ) ) ) when: "GET request is sent" def response = requestSpecification .header("X-Auth-Token", "${accessToken}") .when() .port(this.port) .get("${ARTISTS_ENDPOINT}/${artist.id}") def responseJson = new JsonSlurper().parseText(response.body().asString()) then: "The response is correct" response.then() .assertThat() .statusCode(HttpStatus.OK.value()) and: "response contains the id of Artist asked for" responseJson.id }

Similarly, we can write a test spec to test and document POST method (creating an Artist) as shown below. Remember, I have secured this method to role: ROLE_ADIN. So, it requires admin to be authenticated first to get a security token and then pass the security token in the subsequent secured requests like POST. The following is the complete test specification with a helper method added to authenticate the user:

/** * Helper method, authenticates the given user and returns the security token. * * @param username the user id * @param password the password * @return security token once successfully authenticated */ protected String authenticateUser(String username, String password) { String authResponse = RestAssured.given() .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE) .body(""" {"username" : "$username", "password" : "$password"} """) .when() .port(this.port) .post(LOGIN_ENDPOINT) .body() .asString() return new JsonSlurper().parseText(authResponse).'access_token' } void "Test and document create Artist request (POST request, save action) to end-point: /api/artists"() { given: "current number of Artists" int nArtists = Artist.count() and: "admin logs in by a POST request to end-point: /api/login" String accessToken = authenticateUser('admin', 'admin') and: "documentation specification for creating an Artist" RequestSpecification requestSpecification = RestAssured.given(this.documentationSpec) .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE) .filter( RestAssuredRestDocumentation.document( 'artists-create-example', PayloadDocumentation.requestFields( PayloadDocumentation.fieldWithPath('firstName').description('Artist first name'), PayloadDocumentation.fieldWithPath('lastName').description('Artist last name') ), PayloadDocumentation.responseFields( PayloadDocumentation.fieldWithPath('id').type(JsonFieldType.STRING).description('Artist id'), PayloadDocumentation.fieldWithPath('firstName').type(JsonFieldType.STRING).description('Artist first name'), PayloadDocumentation.fieldWithPath('lastName').type(JsonFieldType.STRING).description('Artist last name'), PayloadDocumentation.fieldWithPath('dateCreated').type(JsonFieldType.STRING).description("Date Created (format:MM/dd/yyyy)"), PayloadDocumentation.fieldWithPath('lastUpdated').type(JsonFieldType.STRING).description("Last Updated Date (format:MM/dd/yyyy)") ) ) ) when: "POST request is sent with valid data" def response = requestSpecification .header("X-Auth-Token", "${accessToken}") .body("""{ "firstName" : "Bhuvan", "lastName" : "Pottepalem" }""") .when() .port(this.port) .post(ARTISTS_ENDPOINT) def responseJson = new JsonSlurper().parseText(response.body().asString()) then: "The response is correct" response.then() .assertThat() .statusCode(HttpStatus.CREATED.value()) and: "response contains the id of Artist created" responseJson.id and: "Number of Artists in the system goes up by one" Artist.count() == nArtists + 1 }

Now, simply run
./gradlew asciidoctor

We will have API docs generated under build/asciidoc dir. Open api-guide.html in a browser to see how nicely the generated API doc looks.

TIP: The beauty of Spring REST Docs framework is that, if compares the actual request/response fields with the PayloadDocumentation filed descriptions and will fail the test if any field(s) are missed or mis-matched. This ensures that the API documentation is up-to-date with the implementation.

Step 4: Publish API
Now, we have fully integrated REST Assured and Spring REST Docs into integrationTest phase with an added asciidoctor Gradle test task. The result of this is an up-to-date API document generated for our Restful service.The API document is the source for clients using this service. So, it needs to be made available. One way to achieve this is to bundle the generated HTML5 API docs with the application's deployable war or executablejar and have it's own end-point to serve it.

Spring Boot (the framework Grails3 underpins) can be leveraged to achieve this. By default Boot serves static content placed under /static or /public in the class path or root of the application context. Here is the link for reference: Spring boot Static content.

Step 4a: Bundle API documentation into deployable artifact
We will enhance our build script (restdocs.gradle) and customize war task that comes with Gradle Java plugin little bit to achieve this. Below is the code snippet which is self explanatory:
/* Bundles generated API docs into war file. * Spring boot serves static content under /public or /static or /resources or /META-INF/resources. * Hooks into war task and adds asciidoctor task dependency, also copies generaed rest docs appropriately * for bundling into war file. */ def publicDocsDir = 'WEB-INF/classes/public/docs' war { dependsOn asciidoctor from ("${asciidoctor.outputDir}") { into publicDocsDir } }

We basically made war task depend on asciidoctor task and added a step to copy generated HTML5 API docs to WEB-INF/classes/public/docs dir in the generated war file.

Now, run grails war to generate deployable war artifact:
grails war

You can explode and see that generated API docs are bundled into the war generated (giri-api-0.1.war):
e.g. jar tvf build/libs/giri-api-0.1.war | grep html will list the following:
 59738 Mon Sep 04 07:16:34 EDT 2017 WEB-INF/classes/public/docs/api-guide.html
 47974 Mon Sep 04 07:16:34 EDT 2017 WEB-INF/classes/public/docs/artists.html

Step 4b: Make API documentation available from it's own end-point
Deploy the generated war file onto a locally running tomcat.
Deploy the war onto locally running Tomcat and point your browser at: http://localhost:8080/giri-api-0.1/static/docs/api-guide.html

This will result into Access Denied error. We need to open up security to serve API docs.

Lets change application.groovy and add /static/docs/** to both grails.plugin.springsecurity.controllerAnnotations.staticRules and filterChainChainMaps as shown below:
grails.plugin.springsecurity.controllerAnnotations.staticRules = [ ... [pattern: '/static/docs/**', access:['permitAll']] ] def filterChainChainMaps = [ ... pattern: '/static/docs/**', filters: statelessFilters], ... ]

Create a war file. Undeploy previously deployed war and deploy the latest war file.

Now, http://localhost:8080/giri-api-0.1/static/docs/api-guide.html (API docs) should be served and displayed by the app.

The test specification can be enhanced easily along these lines to test and document rest of the service methods: show, update and delete available for /api/artists end-point through HTTP methods GET specific resource by id, UPDATE and DELETE respectively.

The complete source code is hosted on GitHub at https://github.com/gpottepalem/giri-api for reference.

References

Sunday, May 28, 2017

Create a Secured Restful API App with Grails 3 and PostgreSQL- Step by Step: Part 4 of 5

Posts on this topic

Part 4: Secure end-points fully and cleanly

I left my last post with basic domain objects (Artist, ArtWork, Specification) created, and one of the core domain objects (Artistexposed as a RESTful resource by leveraging Grails provided @Resource annotation. Without writing any further code, I got all CRUD operations for Artist resource working in RESTful way. That's pretty neat out-of-the-box default implementation provided by Grails framework. I also ended my last post with a note about not-so-readable UUID and date formats. This post is a continuation of previous and is all about securing Artist resource end-point fully.

Environment: Grails 3.1.6, Java 1.8, IntelliJ 15 on Mac OS X 10.9.5

Step 1 First, let's make id, dateCreated and lastUpdated formats more readable
There are multiple ways to customize data formatting.

i) The easiest way is to simply register JSON marshallers as shown below in Bootstrap.groovy. Grails runs grails-app/init/*Bootstrap classes' init closure(s) at the startup of the application.

grails-app/init/Bootstrap.groovy
class BootStrap { def init = { //register JSON marshaller for Date grails.converters.JSON.registerObjectMarshaller(Date){ return it?.format('MM/dd/yyyy') } //register JSON marshaller for UUID grails.converters.JSON.registerObjectMarshaller(UUID){ return it?.toString() } ... } ... }

ii) Another way to register marshallers is by defining a Spring bean that registers all marshallers as shown below:

src/main/groovy/com/giri/marshallers/CustomObjectMarshaller.groovy
package com.giri.marshallers /** * Custom object marshaller trait for all custom object marshallers to implement. */ trait CustomObjectMarshaller { abstract void register() }

src/main/groovy/com/giri/marshallers/UUIDMarshaller.groovy
package com.giri.marshallers import grails.converters.JSON /** * UUID marshaller, registers a {@link JSON} marshaller to output the string representation of {@link UUID} */ class UUIDMarshaller implements CustomObjectMarshaller { @Override void register(){ JSON.registerObjectMarshaller(UUID){ UUID uuid-> return uuid.toString() } } }

src/main/groovy/com/giri/marshallers/DateMarshaller.groovy
package com.giri.marshallers import grails.converters.JSON /** * Date marshaller, registers a {@link JSON} marshaller to output the string representation of {@link Date} */ class DateMarshaller implements CustomObjectMarshaller { @Override void register() { JSON.registerObjectMarshaller(Date) {Date date -> return date.format('MM/dd/yyyy') } } }

src/main/groovy/com/giri/marshallers/CustomMarshallerRegistrar.groovy
package com.giri.marshallers import javax.annotation.PostConstruct /** * Custom Marshaller Registrar, registers custom object marshallers with spring. * Configured as a spring managed bean in resources.groovy * * @see resources.groovy */ class CustomMarshallerRegistrar { /** List of custom marshallers to be registered, initialized with bean configuration in resources.groovy */ List marshallers @PostConstruct void registerCustomMarshallers() { marshallers.each{ it.register() } } }

grails-app/conf/spring/resources.groovy
import com.giri.marshallers.CustomMarshallerRegistrar import com.giri.marshallers.DateMarshaller import com.giri.marshallers.UUIDMarshaller beans = { //JSON Marshallers customMarshallerRegistrar(CustomMarshallerRegistrar) { marshallers = [ new UUIDMarshaller(), new DateMarshaller() ] } }

With this, UUID and date formats in the response look like:
$ curl -i -X GET 'http://localhost:8080/api/artists' HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... [{"id":"8d6698a1-03db-4676-973b-bb374aa1381c","dateCreated":"05/27/2017","firstName":"Giri","lastName":"Pottepalem","lastUpdated":"05/27/2017"}]

iii) There is even a better way of customizing the response using Grails recent addition- JSON views, which is not covered in this post.

Step 2 Secure Resource end-point

Let's start securing Artist resource end-point.

When domain class is annotated with @Resource, Grails provides  RestfulController implementation for CRUD actions, maps them to appropriate HTTP method verbs and makes the resource accessible at the end-point specified through uri property of @Resource annotation in RESTful way.

In addition, Spring security core plugin's @Secured annotation can be applied on the domain object to secure the resource. In my previous post's Step 9, I allowed everyone access to /api/artists end-point by annotating Artist domain object with @Secured(['permitAll']). With this all CRUD operations are allowed without a login. We need to secure this resource now.

Let's say we want to allow only Admin user to access Artist resource. This can easily be done by changing the annotation to @Secured(['ROLE_ADMIN']) or simply to @Secured('ROLE_ADMIN')

With that, the end-point /api/artists is now secured and is accessible to only users Admin role. Let's test it.

Get Artists
$ curl -i -X GET 'http://localhost:8080/api/artists' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 Content-Type: application/json;charset=UTF-8 ...

Login as Admin
$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"username":"admin","password":"admin"}' http://localhost:8080/api/login HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Cache-Control: no-store Pragma: no-cache Content-Type: application/json;charset=UTF-8 Content-Length: 93 Date: Wed, 24 May 2017 22:14:07 GMT {"username":"admin","roles":["ROLE_ADMIN"],"access_token":"ucsbqbd3f26fjpb5b6794ph7cbu3fqq2"}

Get Artists as logged in Admin
$ curl -i -H "X-Auth-Token: ucsbqbd3f26fjpb5b6794ph7cbu3fqq2" http://localhost:8080/api/artists HTTP/1.1 200 OK Server: Apache-Coyote/1.1 X-Application-Context: application:development Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Wed, 24 May 2017 22:15:13 GMT []

Post an Artist
$ curl -i -X POST -H "Content-Type:application/json" -d '{ "firstName": "Giri", "lastName": "Pottepalem" }' 'http://localhost:8080/api/artists' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Wed, 24 May 2017 22:25:52 GMT {"timestamp":1495664752925,"status":403,"error":"Forbidden","message":"Access Denied","path":"/api/artists"}

Post an Artist as logged in Admin
$ curl -i -X POST -H "X-Auth-Token: ucsbqbd3f26fjpb5b6794ph7cbu3fqq2" -H "Content-Type: application/json" -d '{ "firstName": "Giri", "lastName": "Pottepalem" }' 'http://localhost:8080/api/artists' HTTP/1.1 201 Created Server: Apache-Coyote/1.1 X-Application-Context: application:development Location: http://localhost:8080/api/artists/a0480de2-d5df-43eb-a919-196e34c40ab5 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Wed, 24 May 2017 22:53:07 GMT {"id":"a0480de2-d5df-43eb-a919-196e34c40ab5","dateCreated":"05/24/2017","firstName":"Giri","lastName":"Pottepalem","lastUpdated":"05/24/2017"}

Step 3 Secure Resource end-point properly and fully

Though @Secured('ROLE_ADMIN') makes the resource secured easily to the role specified, this may not meet the actual security requirements. Let's say, the right level of security we want to apply to the end-point: /api/artists is as follows:
  • Allow everyone to see the list of Artists
  • Only allow admin to create/delete an Artist
  • A logged in Artist can only see/update his/her details
We have now specific security logic that we need to apply to different actions on the resource. This level of customization is not possible with @Secured annotation applied at the resource-level. It requires some customization at the action level and this is where we can implement our own REST controller for the resource to achieve this. Grails comes with grails.rest.RestfulController base implementation that can be extended. This is not required but gives you some common base logic that can be leveraged.

Let's generate a REST controller now for the resource/domain object. Grails 3 offers create-restful-controller command for creating a RESTful controller.

$ grails create-restful-controller com.giri.Artist | Created grails-app/controllers/com/giri/ArtistController.groovy

The generated class looks like:
package com.giri import grails.rest.* import grails.converters.* class ArtistController extends RestfulController { static responseFormats = ['json', 'xml'] ArtistController() { super(Artist) } }

The generated controller is minimal with default implementation for all actions derived from the base RestfulController class provided by Grails. With this controller in place for our customization we no longer need @Resource and @Secured annotations on the domain class. Let's remove those and add URL mappings in UrlMappings.groovy for the resource.

grails-app/contrllers/giri/api/UrlMappings.groovy
package giri.api class UrlMappings { static mappings = { ... "/api/artists"(resources: 'artist') }

With this we can run grails url-mappings-report command to check url mappings for the resource end-point. It should look same as it was with @Resource applied on the Artist domain class. Now we can provide necessary action-methods implementations and secure each action-method with @Secured annotation and achieve custom security that we wanted for the end-point's each HTTP verb mapped to a specific action-method.

The customized ArtistController class looks like(with custom security highlighted):
package com.giri import grails.plugin.springsecurity.annotation.Secured import grails.rest.RestfulController import grails.transaction.Transactional /** * Customized Artists RestfulController. * * @author Gpottepalem * Created on May 26, 2017 */ class ArtistController extends RestfulController { static responseFormats = ['json', 'xml'] ArtistController() { super(Artist) } @Secured('permitAll') @Override def index(Integer max) { super.index(max) } @Secured('ROLE_USER') @Override def show() { super.show() } @Secured('ROLE_ADMIN') @Override def save() { super.save() } @Secured('ROLE_USER') @Override def update() { super.update() } @Secured('ROLE_ADMIN') @Override def delete() { super.delete() } }
Not much customization is done to achieve this other than overwriting just needed action-methods and annotating them properly per security requirements. All overwritten methods simple delegate the implementation to the super class.
Gotcha: There is no need for @Transactional annotation for methods like save(), update() and delete() as they all simply call corresponding super methods and all super methods are annotated appropriately for transactionality. In fact, annotating these methods again in this kind of implementation results into exception ;)

Step 4 Test fully customized Resource end-point

With the required customization done, let's take a spin and test it. Note that I have bootstrapped an admin user and a me user as specified in Step 4 of my earlier post.

# GET Artists (index) $ curl -i -X GET 'http://localhost:8080/api/artists' HTTP/1.1 200 OK ... []

# Login as Admin $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"username":"admin","password":"admin"}' http://localhost:8080/api/login HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... {"username":"admin","roles":["ROLE_ADMIN"],"access_token":"h1tdbs1cc8e7qt1bt7ohpsar57nt8car"}

# Login as me user $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"username":"me","password":"password"}' http://localhost:8080/api/login HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... {"username":"me","roles":["ROLE_USER"],"access_token":"ci9ct5hocreljl5pbqga60npsi8ol03f"}

# POST Artist as user (save) $ curl -i -X POST -H "X-Auth-Token: ci9ct5hocreljl5pbqga60npsi8ol03f" -H "Content-Type: application/json" -d '{ "firstName": "Giri", "lastName": "Potte" }' 'http://localhost:8080/api/artists' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 ... {"timestamp":1495891839637,"status":403,"error":"Forbidden","message":"Access is denied","path":"/api/artists"}

# POST Artist as admin (save) $ curl -i -X POST -H "X-Auth-Token: h1tdbs1cc8e7qt1bt7ohpsar57nt8car" -H "Content-Type: application/json" -d '{ "firstName": "Giri", "lastName": "Potte" }' 'http://localhost:8080/api/artists' HTTP/1.1 201 Created Server: Apache-Coyote/1.1 ... {"id":"8d6698a1-03db-4676-973b-bb374aa1381c","dateCreated":"05/27/2017","firstName":"Giri","lastName":"Potte","lastUpdated":"05/27/2017"}

# GET Artists (index) $ curl -i -X GET 'http://localhost:8080/api/artists' HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... [{"id":"8d6698a1-03db-4676-973b-bb374aa1381c","dateCreated":"05/27/2017","firstName":"Giri","lastName":"Potte","lastUpdated":"05/27/2017"}]

# GET an Artist (show) $ curl -i -X GET 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 ... {"timestamp":1495893259443,"status":403,"error":"Forbidden","message":"Access Denied","path":"/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c"}

# GET an Artist (show) as admin - secured for ROLE_USER $ curl -i -X GET -H "X-Auth-Token: h1tdbs1cc8e7qt1bt7ohpsar57nt8car" 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 ... {"timestamp":1495893471587,"status":403,"error":"Forbidden","message":"Access is denied","path":"/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c"}

# GET an Artist (show) as me user - secured for ROLE_USER $ curl -i -X GET -H "X-Auth-Token: ci9ct5hocreljl5pbqga60npsi8ol03f" 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... {"id":"8d6698a1-03db-4676-973b-bb374aa1381c","dateCreated":"05/27/2017","firstName":"Giri","lastName":"Potte","lastUpdated":"05/27/2017"}

# PUT Artist as admin (update) $ curl -i -X PUT -H "X-Auth-Token: h1tdbs1cc8e7qt1bt7ohpsar57nt8car" -H "Content-Type: application/json" -d '{ "lastName": "Pottepalem" }' 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 403 Forbidden ... {"timestamp":1495892176757,"status":403,"error":"Forbidden","message":"Access is denied","path":"/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c"}

# PUT Artist as me user (update) $ curl -i -X PUT -H "X-Auth-Token: ci9ct5hocreljl5pbqga60npsi8ol03f" -H "Content-Type: application/json" -d '{ "lastName": "Pottepalem" }' 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... {"id":"8d6698a1-03db-4676-973b-bb374aa1381c","dateCreated":"05/27/2017","firstName":"Giri","lastName":"Pottepalem","lastUpdated":"05/27/2017"}

# GET Artists (index) $ curl -i -X GET 'http://localhost:8080/api/artists' HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... [{"id":"8d6698a1-03db-4676-973b-bb374aa1381c","dateCreated":"05/27/2017","firstName":"Giri","lastName":"Pottepalem","lastUpdated":"05/27/2017"}]

# DELETE Artist as user (delete) $ curl -i -X DELETE -H "X-Auth-Token: ci9ct5hocreljl5pbqga60npsi8ol03f" 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 ... {"timestamp":1495892582172,"status":403,"error":"Forbidden","message":"Access is denied","path":"/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c"}

# DELETE Artist as admin (delete) $ curl -i -X DELETE -H "X-Auth-Token: h1tdbs1cc8e7qt1bt7ohpsar57nt8car" 'http://localhost:8080/api/artists/8d6698a1-03db-4676-973b-bb374aa1381c' HTTP/1.1 204 No Content Server: Apache-Coyote/1.1 ...

# GET Artists (index) $ curl -i -X GET 'http://localhost:8080/api/artists' HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ... []

# Logout admin $ curl -i -X POST -H "X-Auth-Token: h1tdbs1cc8e7qt1bt7ohpsar57nt8car" http://localhost:8080/api/logout HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ...

# Logout user $ curl -i -X POST -H "X-Auth-Token: ci9ct5hocreljl5pbqga60npsi8ol03f" http://localhost:8080/api/logout HTTP/1.1 200 OK Server: Apache-Coyote/1.1 ...

Everything looks good except that any logged-in user can see/update any other user as we only secured update method to ROLE_USER. We can easily add some custom logic to show() and update() action-methods to lock-down these actions further so that a logged-in user can only see/update his/her own user. Grails Spring Security core plugin provides SpringSecurityService class that can be leveraged to achieve this. Since the basic domain model currently I have has not evolved enough for making this check, I am only showing pseudo-coding-steps here:
class ArtistController extends RestfulController { SpringSecurityService springSecurityService ... def update() { AppUser currentUser = springSecurityService.currentUser as AppUser AppUser updateArtist = //Find user account of the Artist's id (params.id) being updated if(currentUser != updateArtist) { respond([message: 'Access Denied'], status: HttpStatus.FORBIDDEN) return } else { ... } }

Gotchas

Grails Spring Security Core plugin's login form
The default Grails Spring Security Core plugin provided login action url: /login/auth when accessed runs into an exception upon not finding an associated view resulting into Internal Server Error response. This is available due to "/$controller/$action?/$id?(.$format)?" mapping in UrlMappings.groovy and /login/auth is mapped to LoginController's auth() action-method provided by Grails Spring Security Core plugin. There is no point in having this wide-open anymore as it provides a form-based login for web application which is not used with Grails Spring Security REST plugin. So, let's lock it down.

When /login/auth is accessed, it runs into the following exception:
javax.servlet.ServletException: Could not resolve view with name '/login/auth' in servlet with name 'grailsDispatcherServlet'

And the response looks like:
$ curl -i -X GET 'http://localhost:8080/login/auth' HTTP/1.1 500 Internal Server Error Server: Apache-Coyote/1.1 ... Connection: close {"message":"Internal server error","error":500}

Lock it down by adding the following pattern to staticRules in application.groovy
grails.plugin.springsecurity.controllerAnnotations.staticRules = [ [pattern: '/login/auth', access: ['denyAll']] //lock down spring security login form url ... ] //Spring Security REST API plugin config String statelessFilters = 'JOINED_FILTERS, -exceptionTranslationFilter, -authenticationProcessingFilter, -securityContextPersistenceFilter, -rememberMeAuthenticationFilter' //common def filterChainChainMaps = [ //Stateless chain [pattern: '/api/**', filters: statelessFilters], [pattern: '/**', filters: statelessFilters] //Traditional stateful chain - We are stateless, no stateful chain is required ] grails.plugin.springsecurity.filterChain.chainMap = filterChainChainMaps

With this, when we access /login/auth, we get the following response:
$ curl -i -X GET 'http://localhost:8080/login/auth' HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 ... {"timestamp":1495926839720,"status":403,"error":"Forbidden","message":"Access Denied","path":"/login/auth"}

Using Custom subclass of RestfulController with @Resource annotation
If you prefer to annotate your custom RestfulController with @Resource instead of mapping the resource in UrlMappings.groovy, there is a small section in Grails docs that describes how to get this done. However, it has some limitations at the time of my exploration as I had to place the controller under src/main/groovy instead of under grails-app/controllers.

References