Skip to content

Commit

Permalink
Change Authorization to use Open API
Browse files Browse the repository at this point in the history
Removing basic authentication in favor of access token, client token, client secret combination.  For information on how to upgrade see https://developer.akamai.com/introduction/Prov_Creds.html and the Readme.

Various other fixes
* Fixed issue with PurgeStatus init - type of some fields didn't match type in json and cannot set a primative to be null.
* Upgrading groovy version
* Removing unessary public and return statments and semicolons. Class.class is not needed.
* Ditching defunct gmaven for the eclipse groovy plugin.
* Preparing version 2
* Use HTTP Status code constant for clarity of meaning
  • Loading branch information
kairas committed Jun 17, 2016
1 parent 0bb9c30 commit d463b9e
Show file tree
Hide file tree
Showing 13 changed files with 373 additions and 189 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

## Description

This bundle provides a "ready to use" OSGI connector for the new Akamai CCU REST API. This connector has been written in groovy using the http-builder framework.
This bundle provides a "ready to use" OSGI connector for the new Akamai Open CCU V2 REST API. This connector has been written in groovy using the http-builder framework.
It is designed to enable cache invalidation for AEM/CQ CMS when assets get invalidated. It can be easily configured with your own credentials
and settings. The connector provides all services that you can request via the REST API like getPurgeStatus(), getQueueStatus(), and the most important purge().

The bundle is made to be as light as possible and can be installed just by itself. You will need groovy-all to be installed along with some others bundles like
The bundle is made to be as light as possible and can be installed just by itself. You will need groovy-all version 2.4.7 to be installed along with some others bundles like
httpclient, commons-collections, commons-lang ... but they usually are already there.

## Implementation
Expand Down Expand Up @@ -45,12 +45,13 @@ Each of these classes can be configured to fit you need and your Akamai credenti
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="sling:OsgiConfig"
rootCcuUrl="https://api.ccu.akamai.com"
userName="your_username"
password="your_password"
clientToken="your_clientSecret"
clientSecret="your_clientSecret"
accessToken="your_accessToken"
defaultPurgeAction="remove"
defaultPurgeDomain="production"/>
```
userName/password: The credentials that you use to connect to the Akamai control website.
For more info on credentials see https://developer.akamai.com/introduction/Prov_Creds.html

defaultPurgeAction : The default purge if not specified.
- remove: (default) Remove the asset from the edge server and force the next request to the asset to reach the origin.
Expand Down Expand Up @@ -93,4 +94,4 @@ This project is open source under MIT License.

## More information

If you want to learn more about the CCU REST API: https://api.ccu.akamai.com/ccu/v2/docs/index.html
If you want to learn more about the CCU REST API: https://developer.akamai.com/introduction/
87 changes: 56 additions & 31 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>com.velir.aem</groupId>
<artifactId>akamai-ccu-rest-api-connector</artifactId>
<version>1.1-SNAPSHOT</version>
<version>2.0-SNAPSHOT</version>
<name>Akamai CCU REST API Connector</name>
<description>Provide a osgi bundle to manage requests to the Akamai CCU REST API.</description>
<url>https://github.com/Velir/akamai-ccu-rest-api-connector</url>
Expand Down Expand Up @@ -60,9 +60,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.jdk>1.6</project.build.jdk>
<slf4j.version>1.6.4</slf4j.version>
<groovy.version>2.1.3</groovy.version>
<gmaven.version>1.4</gmaven.version>
<gmaven.provider.version>2.0</gmaven.provider.version>
<groovy.version>2.4.7</groovy.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -148,6 +146,13 @@
<scope>provided</scope>
</dependency>

<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.5</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
Expand All @@ -158,7 +163,7 @@
<dependency>
<groupId>org.codehaus.groovy.modules.http-builder</groupId>
<artifactId>http-builder</artifactId>
<version>0.7</version>
<version>0.7.1</version>
</dependency>

<dependency>
Expand Down Expand Up @@ -259,7 +264,36 @@
</dependencies>

<build>
<sourceDirectory>src/main/groovy</sourceDirectory>
<testSourceDirectory>src/test/groovy</testSourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerId>groovy-eclipse-compiler</compilerId>
<source>1.7</source>
<target>1.7</target>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-compiler</artifactId>
<version>2.9.0-01</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-batch</artifactId>
<version>2.3.4-01</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
Expand Down Expand Up @@ -292,7 +326,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
<version>1.16.0</version>
<version>1.17.0</version>
<executions>
<execution>
<id>generate-scr-scrdescriptor</id>
Expand All @@ -301,18 +335,34 @@
</goals>
</execution>
</executions>
<configuration>
<scanClasses>true</scanClasses>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<compilerId>groovy-eclipse-compiler</compilerId>
<source>${project.build.jdk}</source>
<target>${project.build.jdk}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-compiler</artifactId>
<version>2.9.0-01</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-batch</artifactId>
<version>2.3.4-01</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand All @@ -326,31 +376,6 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>${gmaven.version}</version>
<executions>
<execution>
<configuration>
<providerSelection>${gmaven.provider.version}</providerSelection>
</configuration>
<goals>
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
10 changes: 5 additions & 5 deletions src/main/groovy/com/velir/aem/akamai/ccu/Activator.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import org.slf4j.LoggerFactory
* @author Sebastien Bernard
*/
class Activator implements BundleActivator {
private static final Logger LOG = LoggerFactory.getLogger(Activator.class);
private static final Logger LOG = LoggerFactory.getLogger(Activator)

public void start(final BundleContext context) throws Exception {
LOG.info(context.getBundle().getSymbolicName() + " started");
void start(final BundleContext context) throws Exception {
LOG.info(context.getBundle().getSymbolicName() + " started")
}

public void stop(final BundleContext context) throws Exception {
LOG.info(context.getBundle().getSymbolicName() + " stopped");
void stop(final BundleContext context) throws Exception {
LOG.info(context.getBundle().getSymbolicName() + " stopped")
}
}
7 changes: 4 additions & 3 deletions src/main/groovy/com/velir/aem/akamai/ccu/PurgeResponse.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.velir.aem.akamai.ccu

import groovy.transform.ToString

import static org.apache.http.HttpStatus.SC_CREATED
/**
* PurgeStatus -
*
Expand All @@ -17,11 +18,11 @@ class PurgeResponse {
long pingAfterSeconds
String supportId

public static PurgeResponse noResponse() {
return new PurgeResponse(httpStatus: -1, detail: "Nothing has been sent because the query was not valid")
static PurgeResponse noResponse() {
new PurgeResponse(httpStatus: -1, detail: "Nothing has been sent because the query was not valid")
}

boolean isSuccess() {
return httpStatus == 201
httpStatus == SC_CREATED
}
}
6 changes: 3 additions & 3 deletions src/main/groovy/com/velir/aem/akamai/ccu/PurgeStatus.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ class PurgeStatus {
String purgeId
String supportId
int httpStatus
long completionTime
String completionTime
String submittedBy
String purgeStatus
String submissionTime
long pingAfterSeconds

public static PurgeStatus noStatus(){
return new PurgeStatus(httpStatus: -1, purgeStatus: "The request was not sent")
static PurgeStatus noStatus(){
new PurgeStatus(httpStatus: -1, purgeStatus: "The request was not sent")
}
}
19 changes: 19 additions & 0 deletions src/main/groovy/com/velir/aem/akamai/ccu/Timestamp.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.velir.aem.akamai.ccu

import groovy.transform.CompileStatic

/**
* Timestamp - Date Formatter
*
* @author Kai Rasmussen
*/
@CompileStatic
class Timestamp {
private static final String CCU_FORMAT = "yyyyMMdd'T'HH:mm:ssZ"
public static final String UTC = "UTC"
public static final TimeZone UTCTZ = TimeZone.getTimeZone(UTC)

static String getTimestamp(Date date){
date.format(CCU_FORMAT, UTCTZ)
}
}
38 changes: 38 additions & 0 deletions src/main/groovy/com/velir/aem/akamai/ccu/auth/Authorization.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.velir.aem.akamai.ccu.auth

import com.velir.aem.akamai.ccu.Timestamp
import com.velir.aem.akamai.ccu.impl.CcuManagerImpl
import groovy.transform.builder.Builder
import groovyx.net.http.Method

import static java.util.UUID.randomUUID

/**
* Authorization -
*
* @author Kai Rasmussen
*/
@Builder
class Authorization {
CcuManagerImpl.Credentials credentials
String path, rootCcuUrl
HashMap body, headers
Method method

String getAuthorization(){
String timeStamp = use(Timestamp){ new Date().timestamp }
String nonce = randomUUID().toString()
String unsignedAuth = "EG1-HMAC-SHA256 client_token=${credentials.clientToken};access_token=${credentials.accessToken};timestamp=${timeStamp};nonce=${nonce};"
String signedAuth = signAuth(path, unsignedAuth, timeStamp, body, method, headers)
"${unsignedAuth}signature=${signedAuth}"
}

private String signAuth(String path, String auth, String timestamp, HashMap body, Method method, HashMap headers) {
Signature sigBuilder = Signature.builder()
.secret(credentials.clientSecret).scheme("https").path(path)
.timestamp(timestamp).host(rootCcuUrl.replaceFirst("https://", ""))
.requestHeaders(headers).postBody(body).method(method).auth(auth)
.build()
sigBuilder.signature
}
}
71 changes: 71 additions & 0 deletions src/main/groovy/com/velir/aem/akamai/ccu/auth/Signature.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.velir.aem.akamai.ccu.auth

import groovy.json.JsonOutput
import groovy.transform.builder.Builder
import groovyx.net.http.Method

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.security.MessageDigest

import static groovyx.net.http.Method.POST
import static javax.crypto.Mac.getInstance
import static org.apache.commons.codec.binary.Base64.encodeBase64String
import static org.apache.commons.lang.StringUtils.EMPTY
/**
* AuthorizationBuilder - Responsible for translating a request into a signature
*
* @author Kai Rasmussen
*/

@Builder
class Signature {
private static final String HMAC_ALG = "HmacSHA256"
private static final String CHARSET = "UTF-8"
public static final String MD_ALG = "SHA-256"

String secret, auth, scheme, host, path, timestamp
HashMap requestHeaders, postBody
Method method

String getSignature() {
String signingKey = sign(timestamp, secret.getBytes(CHARSET))
String toSign = "${canonicalRequest}${auth}"
sign(toSign, signingKey.getBytes(CHARSET))
}

private String getCanonicalRequest(){
"${method.toString()}\t${scheme}\t${host}\t${path}\t${canonicalizeHeaders}\t${contentHash}\t"
}

private String getCanonicalizeHeaders(){
requestHeaders?requestHeaders.inject(''){ str, key, value ->
value = (value.trim() =~ /s+/).replaceAll(' ')
if(value){
str += "${key.toLowerCase()}:${value}\t"
}
} : EMPTY
}

private String getContentHash(){
String hash = EMPTY
if(method == POST && postBody){
String body = JsonOutput.toJson(postBody)
MessageDigest md = MessageDigest.getInstance(MD_ALG)
byte[] bytes = body.bytes
md.update(bytes, 0, bytes.length)
byte[] digest = md.digest()
hash = encodeBase64String(digest)
}
hash
}

private static String sign(String s, byte[] key) {
SecretKeySpec signingKey = new SecretKeySpec(key, HMAC_ALG)
Mac mac = getInstance(HMAC_ALG)
mac.init(signingKey)
byte[] valueBytes = s.getBytes(CHARSET)
byte[] bytes = mac.doFinal(valueBytes)
encodeBase64String(bytes)
}
}
Loading

0 comments on commit d463b9e

Please sign in to comment.