Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth Token specs #26

Merged
merged 14 commits into from
Oct 16, 2015
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
RSA15 has changed
  • Loading branch information
ricardopereira committed Oct 14, 2015
commit dd2baf01a18222ae4d0d811a6fc40b774905d8d7
3 changes: 2 additions & 1 deletion ably-ios/ARTAuth.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ typedef NS_ENUM(NSUInteger, ARTAuthMethod) {

@property (nonatomic, weak) ARTLog *logger;

@property (nonatomic, readonly, strong) ARTAuthTokenDetails *tokenDetails;
@property (art_nullable, readonly, getter=getClientId) NSString *clientId;
@property (art_nullable, nonatomic, readonly, strong) ARTAuthTokenDetails *tokenDetails;

// FIXME: review (Why rest?)
- (instancetype)init:(ARTRest *)rest withOptions:(ARTClientOptions *)options;
Expand Down
17 changes: 17 additions & 0 deletions ably-ios/ARTAuth.m
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,21 @@ - (BOOL)canRequestToken {
}
}

- (NSString *)getClientId {
if (self.tokenDetails) {
// Check wildcard
if ([self.tokenDetails.clientId isEqual:@"*"])
// Any client
return nil;
else
return self.tokenDetails.clientId;
}
else if (self.options) {
return self.options.clientId;
}
else {
return nil;
}
}

@end
6 changes: 6 additions & 0 deletions ably-ios/ARTAuthOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ ART_ASSUME_NONNULL_BEGIN
*/
@property (readwrite, assign, nonatomic) BOOL force;

/**
The id of the client represented by this instance.
The clientId is relevant to presence operations, where the clientId is the principal identifier of the client in presence update messages. The clientId is also relevant to authentication; a token issued for a specific client may be used to authenticate the bearer of that token to the service.
*/
@property (readwrite, strong, nonatomic) NSString *clientId;

- (instancetype)init;
- (instancetype)initWithKey:(NSString *)key;
- (instancetype)initDefaults;
Expand Down
3 changes: 3 additions & 0 deletions ably-ios/ARTAuthOptions.m
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ - (id)copyWithZone:(NSZone *)zone {
options.queryTime = self.queryTime;
options.useTokenAuth = self.useTokenAuth;
options.force = self.force;
options.clientId = self.clientId;

return options;
}
Expand Down Expand Up @@ -105,6 +106,8 @@ - (ARTAuthOptions *)mergeWith:(ARTAuthOptions *)precedenceOptions {
merged.useTokenAuth = precedenceOptions.useTokenAuth;
if (precedenceOptions.force)
merged.force = precedenceOptions.force;
if (precedenceOptions.clientId)
merged.clientId = precedenceOptions.clientId;

return merged;
}
Expand Down
8 changes: 2 additions & 6 deletions ably-ios/ARTClientOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,20 @@

#import <Foundation/Foundation.h>
#import "ARTAuthOptions.h"
#import "ARTLog.h"

ART_ASSUME_NONNULL_BEGIN

@interface ARTClientOptions : ARTAuthOptions

/**
The id of the client represented by this instance.
The clientId is relevant to presence operations, where the clientId is the principal identifier of the client in presence update messages. The clientId is also relevant to authentication; a token issued for a specific client may be used to authenticate the bearer of that token to the service.
*/
@property (readwrite, strong, nonatomic) NSString *clientId;

@property (readonly, getter=getRestHost) NSString *restHost;
@property (readonly, getter=getRealtimeHost) NSString *realtimeHost;

@property (nonatomic, assign, nonatomic) int restPort;
@property (nonatomic, assign, nonatomic) int realtimePort;
@property (readwrite, strong, nonatomic) NSString *environment;
@property (nonatomic, assign) BOOL tls;
@property (nonatomic, assign) ARTLogLevel logLevel;

@property (readwrite, assign, nonatomic) BOOL queueMessages;
@property (readwrite, assign, nonatomic) BOOL echoMessages;
Expand Down
5 changes: 3 additions & 2 deletions ably-ios/ARTClientOptions.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ - (instancetype)initDefaults {
_binary = false;
_autoConnect = true;
_tls = YES;
_logLevel = ARTLogLevelNone;
return self;
}

Expand Down Expand Up @@ -65,8 +66,7 @@ - (bool)isFallbackPermitted {

- (id)copyWithZone:(NSZone *)zone {
ARTClientOptions *options = [super copyWithZone:zone];

options.clientId = self.clientId;

options.restPort = self.restPort;
options.realtimePort = self.realtimePort;
options.queueMessages = self.queueMessages;
Expand All @@ -78,6 +78,7 @@ - (id)copyWithZone:(NSZone *)zone {
options.resumeKey = self.resumeKey;
options.environment = self.environment;
options.tls = self.tls;
options.logLevel = self.logLevel;

return options;
}
Expand Down
4 changes: 4 additions & 0 deletions ably-ios/ARTRest.m
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ - (instancetype)initWithLogger:(ARTLog *)logger andOptions:(ARTClientOptions *)o
_logger = [[ARTLog alloc] init];
}

if (options.logLevel != ARTLogLevelNone) {
_logger.logLevel = options.logLevel;
}

_http = [[ARTHttp alloc] init];
_httpExecutor = _http;
_httpExecutor.logger = _logger;
Expand Down
218 changes: 138 additions & 80 deletions ablySpec/Auth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,68 +61,71 @@ class Auth : QuickSpec {

describe("Token") {

// RSA3a
it("should work over HTTPS or HTTP") {
let options = ARTClientOptions()
options.token = getTestToken()

// Check HTTP
options.tls = false
let clientHTTP = ARTRest(options: options)
clientHTTP.httpExecutor = mockExecutor

publishTestMessage(clientHTTP, failOnError: false)

expect(mockExecutor.requests.first).toNot(beNil(), description: "No request found")
expect(mockExecutor.requests.first?.URL).toNot(beNil(), description: "Request is invalid")

if let request = mockExecutor.requests.first, let url = request.URL {
expect(url.scheme).to(equal("http"), description: "No HTTP support")
}

// Check HTTPS
options.tls = true
let clientHTTPS = ARTRest(options: options)
clientHTTPS.httpExecutor = mockExecutor

publishTestMessage(clientHTTPS, failOnError: false)

expect(mockExecutor.requests.last).toNot(beNil(), description: "No request found")
expect(mockExecutor.requests.last?.URL).toNot(beNil(), description: "Request is invalid")

if let request = mockExecutor.requests.last, let url = request.URL {
expect(url.scheme).to(equal("https"), description: "No HTTPS support")
}
}

// RSA3b
it("should send the token in the Authorization header") {
let options = ARTClientOptions()
options.token = getTestToken()
let client = ARTRest(options: options)
client.httpExecutor = mockExecutor

publishTestMessage(client, failOnError: false)

expect(client.options.token).toNot(beNil(), description: "No access token")

if let currentToken = client.options.token {
let token64 = NSString(string: currentToken)
.dataUsingEncoding(NSUTF8StringEncoding)?
.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
// RSA3
context("token auth") {
// RSA3a
it("should work over HTTPS or HTTP") {
let options = ARTClientOptions()
options.token = getTestToken()

// Check HTTP
options.tls = false
let clientHTTP = ARTRest(options: options)
clientHTTP.httpExecutor = mockExecutor

let expectedAuthorization = "Bearer \(token64!)"
publishTestMessage(clientHTTP, failOnError: false)

expect(mockExecutor.requests.first).toNot(beNil(), description: "No request found")
expect(mockExecutor.requests.first?.URL).toNot(beNil(), description: "Request is invalid")

if let request = mockExecutor.requests.first, let url = request.URL {
expect(url.scheme).to(equal("http"), description: "No HTTP support")
}

// Check HTTPS
options.tls = true
let clientHTTPS = ARTRest(options: options)
clientHTTPS.httpExecutor = mockExecutor

publishTestMessage(clientHTTPS, failOnError: false)

let authorization = mockExecutor.requests.first?.allHTTPHeaderFields?["Authorization"] ?? ""
expect(mockExecutor.requests.last).toNot(beNil(), description: "No request found")
expect(mockExecutor.requests.last?.URL).toNot(beNil(), description: "Request is invalid")

expect(authorization).to(equal(expectedAuthorization))
if let request = mockExecutor.requests.last, let url = request.URL {
expect(url.scheme).to(equal("https"), description: "No HTTPS support")
}
}

// RSA3b
it("should send the token in the Authorization header") {
let options = ARTClientOptions()
options.token = getTestToken()
let client = ARTRest(options: options)
client.httpExecutor = mockExecutor

publishTestMessage(client, failOnError: false)

expect(client.options.token).toNot(beNil(), description: "No access token")

if let currentToken = client.options.token {
let token64 = NSString(string: currentToken)
.dataUsingEncoding(NSUTF8StringEncoding)?
.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))

let expectedAuthorization = "Bearer \(token64!)"

expect(mockExecutor.requests.first).toNot(beNil(), description: "No request found")

let authorization = mockExecutor.requests.first?.allHTTPHeaderFields?["Authorization"] ?? ""

expect(authorization).to(equal(expectedAuthorization))
}
}

// RSA3c
//TODO: not implemented
}

// RSA3c
//TODO: not implemented

// RSA4
context("authentication method") {
Expand Down Expand Up @@ -164,32 +167,91 @@ class Auth : QuickSpec {
}
}
}

// RSA15
fit("should check clientId consistency") {
let expectedClientId = "client_string"
let options = AblyTests.setupOptions(AblyTests.jsonRestOptions)
options.clientId = expectedClientId

let client = ARTRest(options: options)
client.httpExecutor = mockExecutor

waitUntil(timeout: 10) { done in
client.authorise { tokenDetails, error in
expect(tokenDetails).toNot(beNil(), description: "TokenDetails is nil")
expect(tokenDetails?.clientId).to(equal(expectedClientId))
done()
context("token auth and clientId") {
// RSA15a
it("should check clientId consistency") {
let expectedClientId = "client_string"
let options = AblyTests.setupOptions(AblyTests.jsonRestOptions)
options.clientId = expectedClientId

let client = ARTRest(options: options)
client.httpExecutor = mockExecutor

waitUntil(timeout: 10) { done in
// Token
client.authorise { tokenDetails, error in
expect(tokenDetails).toNot(beNil(), description: "TokenDetails is nil")
expect(tokenDetails?.clientId).to(equal(expectedClientId))
done()
}
}

switch extractBodyAsJSON(mockExecutor.requests.first) {
case .Failure(let error):
XCTFail(error)
case .Success(let httpBody):
guard let requestedClientId = httpBody.unbox["clientId"] as? String else { XCTFail("No clientId field in HTTPBody"); return }
expect(requestedClientId).to(equal(expectedClientId))
}

// TODO: Realtime.connectionDetails of the CONNECTED ProtocolMessage
}

// TODO: reuse
guard let request = mockExecutor.requests.first else { XCTFail("No request found"); return }
guard let bodyData = request.HTTPBody else { XCTFail("No HTTPBody"); return }
guard let json = try? NSJSONSerialization.JSONObjectWithData(bodyData, options: .MutableLeaves) else { XCTFail("Invalid json"); return }
guard let httpBody = json as? NSDictionary else { XCTFail("HTTPBody has invalid format"); return }
guard let requestedClientId = httpBody["clientId"] as? String else { XCTFail("No clientId field in HTTPBody"); return }
// RSA15b
it("should permit to be unauthenticated") {
let options = AblyTests.setupOptions(AblyTests.jsonRestOptions)
options.clientId = "*"

waitUntil(timeout: 10) { done in
// Token
ARTRest(options: options).authorise { tokenDetails, error in
expect(tokenDetails).toNot(beNil(), description: "TokenDetails is nil")
expect(tokenDetails?.clientId).to(equal(options.clientId))
options.tokenDetails = tokenDetails
done()
}
}

expect(requestedClientId).to(equal(expectedClientId))
waitUntil(timeout: 10) { done in
// Token
ARTRest(options: options).authorise { tokenDetails, error in
expect(tokenDetails).toNot(beNil(), description: "TokenDetails is nil")
expect(tokenDetails?.clientId).to(equal("*"))
done()
}
}

// TODO: Realtime.connectionDetails
}

// RSA15c
it("should cancel request when clientId is invalid") {
let options = AblyTests.setupOptions(AblyTests.jsonRestOptions)

// Check unquoted
options.clientId = "\"client_string\""
waitUntil(timeout: 10) { done in
// Token
ARTRest(options: options).authorise { tokenDetails, error in
expect(tokenDetails).toNot(beNil(), description: "TokenDetails is nil")
expect(tokenDetails?.clientId).to(equal(options.clientId))
done()
}
}

// Check unescaped
options.clientId = "client_string\n"
waitUntil(timeout: 10) { done in
// Token
ARTRest(options: options).authorise { tokenDetails, error in
expect(tokenDetails).toNot(beNil(), description: "TokenDetails is nil")
expect(tokenDetails?.clientId).to(equal(options.clientId))
done()
}
}
}
}

// RSA5
Expand All @@ -203,10 +265,6 @@ class Auth : QuickSpec {
let tokenParams = ARTAuthTokenParams()
expect(tokenParams.capability) == "{\"*\":[\"*\"]}"
}

// RSA7
//TODO

}

// RSA8
Expand Down
Loading