Skip to content

Commit

Permalink
Adds an admin account/password for better security.
Browse files Browse the repository at this point in the history
- On first launch, create an admin account with a randomly 
generated password.
- Disable all anonymous access (even reads).
- New CouchbaseMobile.adminCredential to get the username/
password.

Change-Id: Ibbdd62ce4d5aaafd025a4e4a744486a669a463d8
Reviewed-on: http://review.couchbase.org/10826
Tested-by: Farshid Ghods <farshid.ghods@gmail.com>
Reviewed-by: Jens Alfke <jens@couchbase.com>
  • Loading branch information
snej committed Nov 21, 2011
1 parent 8fbac94 commit bd1f8d0
Show file tree
Hide file tree
Showing 9 changed files with 420 additions and 52 deletions.
16 changes: 16 additions & 0 deletions EmptyApp/Empty App.xcodeproj/project.pbxproj
Expand Up @@ -7,6 +7,10 @@
objects = {

/* Begin PBXBuildFile section */
270B6A55146CA13500AAD650 /* RESTBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = 270B6A54146CA13500AAD650 /* RESTBase64.m */; };
270B6A56146CA13500AAD650 /* RESTBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = 270B6A54146CA13500AAD650 /* RESTBase64.m */; };
270B6A5A146D957A00AAD650 /* TestConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 270B6A59146D957A00AAD650 /* TestConnection.m */; };
270B6A5B146D957A00AAD650 /* TestConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 270B6A59146D957A00AAD650 /* TestConnection.m */; };
273112D8140559B30072C622 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A3ACF513C78BD9008CE9F0 /* main.m */; };
273112D9140559B30072C622 /* EmptyAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A3ACF913C78BD9008CE9F0 /* EmptyAppDelegate.m */; };
273112DB140559B30072C622 /* libstdc++.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 27A3AD1C13C78DAC008CE9F0 /* libstdc++.dylib */; };
Expand Down Expand Up @@ -52,6 +56,10 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
270B6A53146CA13500AAD650 /* RESTBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RESTBase64.h; sourceTree = "<group>"; };
270B6A54146CA13500AAD650 /* RESTBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RESTBase64.m; sourceTree = "<group>"; };
270B6A58146D957A00AAD650 /* TestConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestConnection.h; sourceTree = "<group>"; };
270B6A59146D957A00AAD650 /* TestConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestConnection.m; sourceTree = "<group>"; };
273112E6140559B30072C622 /* Listener.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Listener.app; sourceTree = BUILT_PRODUCTS_DIR; };
273112EA14055A410072C622 /* app.ini */ = {isa = PBXFileReference; explicitFileType = text; fileEncoding = 4; path = app.ini; sourceTree = "<group>"; };
2753C254140D54E300F334A7 /* installResources.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = installResources.sh; sourceTree = "<group>"; };
Expand Down Expand Up @@ -187,6 +195,10 @@
children = (
27A3AD0E13C78BD9008CE9F0 /* EmptyAppTests.h */,
27A3AD1013C78BD9008CE9F0 /* EmptyAppTests.m */,
270B6A58146D957A00AAD650 /* TestConnection.h */,
270B6A59146D957A00AAD650 /* TestConnection.m */,
270B6A53146CA13500AAD650 /* RESTBase64.h */,
270B6A54146CA13500AAD650 /* RESTBase64.m */,
27A3AD0913C78BD9008CE9F0 /* Supporting Files */,
);
path = Tests;
Expand Down Expand Up @@ -379,6 +391,8 @@
27A3ACF613C78BD9008CE9F0 /* main.m in Sources */,
27A3ACFA13C78BD9008CE9F0 /* EmptyAppDelegate.m in Sources */,
27EE5C8614103F50000AB2D7 /* EmptyAppTests.m in Sources */,
270B6A55146CA13500AAD650 /* RESTBase64.m in Sources */,
270B6A5A146D957A00AAD650 /* TestConnection.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -387,6 +401,8 @@
buildActionMask = 2147483647;
files = (
27A3AD1113C78BD9008CE9F0 /* EmptyAppTests.m in Sources */,
270B6A56146CA13500AAD650 /* RESTBase64.m in Sources */,
270B6A5B146D957A00AAD650 /* TestConnection.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
92 changes: 41 additions & 51 deletions EmptyApp/Tests/EmptyAppTests.m
Expand Up @@ -7,6 +7,8 @@
//

#import "EmptyAppTests.h"
#import "RESTBase64.h"
#import "TestConnection.h"
#import <Couchbase/CouchbaseMobile.h>
#import <Couchbase/CouchbaseCallbacks.h>

Expand Down Expand Up @@ -42,45 +44,25 @@ - (void)tearDown
}


- (NSURLRequest*)request: (NSString*)method path: (NSString*)relativePath body: (NSString*)body {
NSURL* url = [NSURL URLWithString: relativePath relativeToURL: sCouchbase.serverURL];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL: url];
request.HTTPMethod = method;
request.cachePolicy = NSURLRequestReloadIgnoringCacheData;
if (body) {
request.HTTPBody = [body dataUsingEncoding: NSUTF8StringEncoding];
[request addValue: @"application/json" forHTTPHeaderField: @"Content-Type"];
}
return request;
}


- (NSString*)send: (NSString*)method
toPath: (NSString*)relativePath
body: (NSString*)body
responseHeaders: (NSDictionary**)outResponseHeaders
{
NSLog(@"%@ %@", method, relativePath);
NSURLRequest* request = [self request:method path:relativePath body:body];
NSHTTPURLResponse* response = nil;
NSError* error = nil;

// This is for testing only! In a real app you would not want to send URL requests synchronously.
NSData* responseBody = [NSURLConnection sendSynchronousRequest: request
returningResponse: (NSURLResponse**)&response
error: &error];
STAssertTrue(responseBody != nil && response != nil,
@"Request to <%@> failed: %@", request.URL.absoluteString, error);
int statusCode = response.statusCode;
TestConnection* conn = [TestConnection connectionWithMethod:method path:relativePath body:body];
[conn run];
STAssertNil(conn.error,
@"Request to <%@> failed: %@", conn.URL.absoluteString, conn.error);
int statusCode = conn.response.statusCode;
STAssertTrue(statusCode < 300,
@"Request to <%@> failed: HTTP error %i", request.URL.absoluteString, statusCode);
@"Request to <%@> failed: HTTP error %i", conn.URL.absoluteString, statusCode);

if (outResponseHeaders)
*outResponseHeaders = response.allHeaderFields;
NSString* responseStr = [[NSString alloc] initWithData: responseBody
encoding: NSUTF8StringEncoding];
*outResponseHeaders = conn.response.allHeaderFields;
NSString* responseStr = conn.responseString;
NSLog(@"Response (%d):\n%@", statusCode, responseStr);
return [responseStr autorelease];
return responseStr;
}

- (NSString*)send: (NSString*)method
Expand All @@ -89,12 +71,23 @@ - (NSString*)send: (NSString*)method
return [self send:method toPath:relativePath body:body responseHeaders:NULL];
}


- (void)forciblyDeleteDatabase {
// No error checking, since this may return a 404
[NSURLConnection sendSynchronousRequest: [self request:@"DELETE" path:@"/unittestdb" body:nil]
returningResponse: NULL
error: NULL];
// No error checking, since this may legitimately return a 404
[[TestConnection connectionWithMethod:@"DELETE" path:@"/unittestdb" body:nil] run];
}


#pragma mark - TESTS


- (void)test0_adminPassword {
NSURLCredential* credential = sCouchbase.adminCredential;
STAssertNotNil(credential, nil);
NSLog(@"Admin username = '%@', password = '%@'", credential.user, credential.password);
STAssertTrue(credential.user.length >= 1, @"username non-empty");
STAssertTrue(credential.password.length >= 32, @"password at least 32 chars");

[self send: @"GET" toPath: @"/_session" body: nil];
}


Expand Down Expand Up @@ -207,7 +200,7 @@ - (void)test5_ObjCViews {
"\"special\": [false, null, true],"
"\"empty array\":[],"
"\"empty dict\": {}}"];

[[CouchbaseCallbacks sharedInstance] registerMapBlock:
^(NSDictionary *doc, CouchEmitBlock emit) {
NSString* txt = [doc objectForKey: @"txt"];
Expand All @@ -230,14 +223,14 @@ - (void)test5_ObjCViews {
}
emit(txt, nil);
} forKey: @"testValuesMap"];

[[CouchbaseCallbacks sharedInstance] registerMapBlock:
^(NSDictionary *doc, CouchEmitBlock emit) {
NSLog(@"In faux map block");
emit(@"objc", nil);
} forKey: @"fauxMap"];


[[CouchbaseCallbacks sharedInstance] registerReduceBlock:
^ id (NSArray *keys, NSArray *values, BOOL rereduce) {
NSLog(@"In reduce block");
Expand Down Expand Up @@ -275,7 +268,8 @@ - (void) test6_ObjCValidation {
[[CouchbaseCallbacks sharedInstance] registerValidateUpdateBlock:
^BOOL(NSDictionary *doc, id<CouchbaseValidationContext> context) {
STAssertEqualObjects(context.databaseName, @"unittestdb", nil);
STAssertNil(context.userName, nil);
NSURLCredential* credential = sCouchbase.adminCredential;
STAssertEqualObjects(context.userName, credential.user, nil);
STAssertTrue(context.isAdmin, nil);
STAssertNotNil(context.security, nil);
BOOL ok = [doc objectForKey: @"valid"] != nil;
Expand All @@ -289,18 +283,14 @@ - (void) test6_ObjCValidation {
[self send: @"PUT" toPath: @"/unittestdb/_design/objcvalidation"
body: @"{\"language\":\"objc\","
@"\"validate_doc_update\":\"VALIDATE\"}"];

[self send: @"PUT" toPath: @"/unittestdb/doc1" body: @"{\"valid\":true}"];

NSURLRequest* request = [self request:@"PUT" path:@"/unittestdb/doc2"
body:@"{\"something\":\"O HAI\"}"];
NSHTTPURLResponse* response = nil;
NSData* output = [NSURLConnection sendSynchronousRequest: request
returningResponse: (NSURLResponse**)&response
error: NULL];
STAssertEquals(response.statusCode, 403, @"Unexpected HTTP status (should be forbidden)");
NSString* outputStr = [[[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding] autorelease];
STAssertEqualObjects(outputStr, @"{\"error\":\"forbidden\",\"reason\":\"totally bogus\"}\n", nil);

TestConnection* conn = [TestConnection connectionWithMethod:@"PUT" path:@"/unittestdb/doc2"
body:@"{\"something\":\"O HAI\"}"];
[conn run];
STAssertEquals(conn.response.statusCode, 403, @"Unexpected HTTP status (should be forbidden)");
STAssertEqualObjects(conn.responseString, @"{\"error\":\"forbidden\",\"reason\":\"totally bogus\"}\n", nil);
}


Expand All @@ -311,7 +301,7 @@ - (void)test7_SSL {
// by iriscouch.com (the "ValiCert Class 2 Policy Validation Authority"), so that this test
// will pass.
[self send: @"PUT" toPath: @"/unittestdb" body: nil];
[self send: @"POST" toPath: @"/_replicate"
[self send: @"POST" toPath: @"/_replicate"
body: @"{\"target\":\"unittestdb\","
"\"source\":\"https://snej.iriscouch.com/intentionally-left-blank\"}"];
}
Expand Down
16 changes: 16 additions & 0 deletions EmptyApp/Tests/RESTBase64.h
@@ -0,0 +1,16 @@
//
// RESTBase64.h
// CouchCocoa
//
// Created by Jens Alfke on 9/14/11.
// Copyright (c) 2011 Couchbase, Inc. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface RESTBase64 : NSObject
+ (NSString*) encode:(const void*) input length:(size_t) length;
+ (NSString*) encode:(NSData*) rawBytes;
+ (NSData*) decode:(const char*) string length:(size_t) inputLength;
+ (NSData*) decode:(NSString*) string;
@end
108 changes: 108 additions & 0 deletions EmptyApp/Tests/RESTBase64.m
@@ -0,0 +1,108 @@
//
// RESTBase64.m
// CouchCocoa
//
// Created by Jens Alfke on 9/14/11.
// Copyright (c) 2011 Couchbase, Inc. All rights reserved.
//

#import "RESTBase64.h"

// Based on public-domain source code by cyrus.najmabadi@gmail.com
// taken from http://www.cocoadev.com/index.pl?BaseSixtyFour


@implementation RESTBase64


static const uint8_t kEncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static int8_t kDecodingTable[256];

+ (void) initialize {
if (self == [RESTBase64 class]) {
memset(kDecodingTable, 0xFF, sizeof(kDecodingTable));
for (NSInteger i = 0; i < sizeof(kEncodingTable); i++) {
kDecodingTable[kEncodingTable[i]] = i;
}
}
}


+ (NSString*) encode: (const void*)input length: (size_t)length {
if (input == NULL)
return nil;
NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
uint8_t* output = (uint8_t*)data.mutableBytes;

for (NSInteger i = 0; i < length; i += 3) {
NSInteger value = 0;
for (NSInteger j = i; j < (i + 3); j++) {
value <<= 8;

if (j < length) {
value |= ((const uint8_t*)input)[j];
}
}

NSInteger index = (i / 3) * 4;
output[index + 0] = kEncodingTable[(value >> 18) & 0x3F];
output[index + 1] = kEncodingTable[(value >> 12) & 0x3F];
output[index + 2] = (i + 1) < length ? kEncodingTable[(value >> 6) & 0x3F] : '=';
output[index + 3] = (i + 2) < length ? kEncodingTable[(value >> 0) & 0x3F] : '=';
}

return [[[NSString alloc] initWithData:data
encoding:NSASCIIStringEncoding] autorelease];
}


+ (NSString*) encode: (NSData*)rawBytes {
return [self encode: rawBytes.bytes length: rawBytes.length];
}


+ (NSData*) decode: (const char*)string length: (size_t)inputLength {
if ((string == NULL) || (inputLength % 4 != 0)) {
return nil;
}

while (inputLength > 0 && string[inputLength - 1] == '=') {
inputLength--;
}

size_t outputLength = inputLength * 3 / 4;
NSMutableData* data = [NSMutableData dataWithLength:outputLength];
uint8_t* output = data.mutableBytes;

NSInteger inputPoint = 0;
NSInteger outputPoint = 0;
while (inputPoint < inputLength) {
uint8_t i0 = string[inputPoint++];
uint8_t i1 = string[inputPoint++];
uint8_t i2 = inputPoint < inputLength ? string[inputPoint++] : 'A'; /* 'A' will decode to \0 */
uint8_t i3 = inputPoint < inputLength ? string[inputPoint++] : 'A';

if (kDecodingTable[i0] < 0 || kDecodingTable[i1] < 0
|| kDecodingTable[i2] < 0 || kDecodingTable[i3] < 0)
return nil;

output[outputPoint++] = (kDecodingTable[i0] << 2) | (kDecodingTable[i1] >> 4);
if (outputPoint < outputLength) {
output[outputPoint++] = ((kDecodingTable[i1] & 0xf) << 4) | (kDecodingTable[i2] >> 2);
}
if (outputPoint < outputLength) {
output[outputPoint++] = ((kDecodingTable[i2] & 0x3) << 6) | kDecodingTable[i3];
}
}

return data;
}


+ (NSData*) decode:(NSString*) string {
NSData* ascii = [string dataUsingEncoding: NSASCIIStringEncoding];
return [self decode: ascii.bytes length: ascii.length];
}


@end
34 changes: 34 additions & 0 deletions EmptyApp/Tests/TestConnection.h
@@ -0,0 +1,34 @@
//
// TestConnection.h
// Empty App
//
// Created by Jens Alfke on 11/11/11.
// Copyright (c) 2011 CouchBase, Inc. All rights reserved.
//

#import <Foundation/Foundation.h>

/** A simple class that sends synchronous HTTP requests using NSURLConnection. */
@interface TestConnection : NSObject <NSURLConnectionDataDelegate>
{
NSMutableURLRequest* _request;
NSHTTPURLResponse* _response;
NSMutableData* _responseBody;
NSError* _error;
BOOL _loading;
}

+ (TestConnection*) connectionWithMethod: (NSString*)method
path: (NSString*)relativePath
body: (NSString*)body;

@property (readonly) NSURL* URL;

- (BOOL) run;

@property (readonly) NSHTTPURLResponse* response;
@property (readonly) NSData* responseBody;
@property (readonly) NSString* responseString;
@property (readonly) NSError* error;

@end

0 comments on commit bd1f8d0

Please sign in to comment.