Permalink
Browse files

Adds an admin account/password for better security.

- 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...
1 parent 8fbac94 commit bd1f8d05b7b66c5634fc609fad85e0f7d7152a56 @snej snej committed Nov 11, 2011
View
16 EmptyApp/Empty App.xcodeproj/project.pbxproj
@@ -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 */; };
@@ -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>"; };
@@ -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;
@@ -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;
};
@@ -387,6 +401,8 @@
buildActionMask = 2147483647;
files = (
27A3AD1113C78BD9008CE9F0 /* EmptyAppTests.m in Sources */,
+ 270B6A56146CA13500AAD650 /* RESTBase64.m in Sources */,
+ 270B6A5B146D957A00AAD650 /* TestConnection.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
92 EmptyApp/Tests/EmptyAppTests.m
@@ -7,6 +7,8 @@
//
#import "EmptyAppTests.h"
+#import "RESTBase64.h"
+#import "TestConnection.h"
#import <Couchbase/CouchbaseMobile.h>
#import <Couchbase/CouchbaseCallbacks.h>
@@ -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
@@ -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];
}
@@ -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"];
@@ -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");
@@ -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;
@@ -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);
}
@@ -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\"}"];
}
View
16 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
View
108 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
View
34 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
View
114 EmptyApp/Tests/TestConnection.m
@@ -0,0 +1,114 @@
+//
+// TestConnection.m
+// Empty App
+//
+// Created by Jens Alfke on 11/11/11.
+// Copyright (c) 2011 CouchBase, Inc. All rights reserved.
+//
+
+#import "TestConnection.h"
+#import <Couchbase/CouchbaseMobile.h>
+
+
+extern CouchbaseMobile* sCouchbase; // Defined in EmptyAppDelegate.m
+
+
+@implementation TestConnection
+
+
+- (id) initWithMethod: (NSString*)method
+ path: (NSString*)relativePath
+ body: (NSString*)body
+{
+ self = [super init];
+ if (self) {
+ NSURL* url = [NSURL URLWithString: relativePath relativeToURL: sCouchbase.serverURL];
+ _request = [[NSMutableURLRequest alloc] initWithURL: url];
+ _request.HTTPMethod = method;
+ _request.cachePolicy = NSURLRequestReloadIgnoringCacheData;
+ if (body) {
+ _request.HTTPBody = [body dataUsingEncoding: NSUTF8StringEncoding];
+ [_request addValue: @"application/json" forHTTPHeaderField: @"Content-Type"];
+ }
+ _responseBody = [[NSMutableData alloc] init];
+ }
+ return self;
+}
+
++ (TestConnection*) connectionWithMethod: (NSString*)method
+ path: (NSString*)relativePath
+ body: (NSString*)body
+{
+ return [[[self alloc] initWithMethod: method path: relativePath body: body] autorelease];
+}
+
+
+- (void)dealloc {
+ [_request release];
+ [_response release];
+ [_error release];
+ [_responseBody release];
+ [super dealloc];
+}
+
+
+@synthesize response=_response, responseBody=_responseBody, error=_error;
+
+- (NSURL*) URL {
+ return _request.URL;
+}
+
+- (NSString*) responseString {
+ return [[[NSString alloc] initWithData: _responseBody encoding: NSUTF8StringEncoding]
+ autorelease];
+}
+
+
+- (BOOL) run
+{
+ NSAssert(!_response && !_error, @"Can't call -run twice");
+ _loading = YES;
+ NSURLConnection* connection = [NSURLConnection connectionWithRequest: _request delegate: self];
+ [connection start];
+ while (_loading) {
+ if (![[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
+ beforeDate: [NSDate distantFuture]])
+ break;
+ }
+ return _error == nil && _response && _response.statusCode < 300;
+}
+
+
+- (void)connection:(NSURLConnection *)connection
+ didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
+{
+ if (challenge.previousFailureCount == 0) {
+ NSURLCredential* credential = sCouchbase.adminCredential;
+ if (credential) {
+ [challenge.sender useCredential: credential forAuthenticationChallenge: challenge];
+ return;
+ }
+ }
+ // give up
+ [challenge.sender cancelAuthenticationChallenge: challenge];
+}
+
+- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
+ _response = (NSHTTPURLResponse*) [response retain];
+}
+
+- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
+ [_responseBody appendData: data];
+}
+
+- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
+ _error = [error retain];
+ _loading = NO;
+}
+
+- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
+ _loading = NO;
+}
+
+
+@end
View
4 Framework/Classes/CouchbaseMobile.h
@@ -81,6 +81,10 @@
/** Defaults to YES, set to NO to prevent auto-restart behavior when app returns from background */
@property (assign) BOOL autoRestart;
+/** A credential containing the admin username and password of the server.
+ These are required in any requests sent to the server. The password is generated randomly on first launch. */
+@property (readonly) NSURLCredential* adminCredential;
+
#pragma mark CONFIGURATION:
/** Initializes the instance with a nonstandard location for the runtime resources.
View
84 Framework/Classes/CouchbaseMobile.m
@@ -24,6 +24,8 @@
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
+#include <CommonCrypto/CommonDigest.h>
+#include <Security/SecRandom.h>
#include <UIKit/UIApplication.h>
// Erlang entry point
@@ -32,6 +34,9 @@
static NSString* const kInternalCouchStartedNotification = @"couchStarted";
static NSString* const kInternalRestartCouchNotification = @"CouchDBRequestRestart";
+#define kAdminUserName @"cbmi-local-admin"
+#define kAdminPasswordPref @"CouchbaseMobileAdmin"
+
static const NSTimeInterval kWaitTimeout = 30.0; // How long to wait for CouchDB to start
@@ -49,6 +54,7 @@ - (BOOL)installTemplateNamed:(NSString*)name
fromDir:(NSString*)fromDir
toDir:(NSString*)toDir;
- (BOOL)deleteFile:(NSString*)filename fromDir: (NSString*)fromDir;
+- (BOOL) setupAdminAccount;
@end
@@ -170,7 +176,7 @@ - (BOOL)start
if(![self createDir: self.logDirectory]
|| ![self createDir: self.databaseDirectory]
- || ![self createFile:self.localIniFilePath contents: @""]
+ || ![self setupAdminAccount]
|| ![self deleteFile:@"couch.uri" fromDir:_rootDirectory])
{
return NO;
@@ -313,6 +319,81 @@ - (void)startupTimeout {
}
+#pragma mark - ADMIN ACCOUNT:
+
+
+- (NSString*) hexOfBytes:(const uint8_t*)bytes length:(size_t)length {
+ char out[2*length+1];
+ char *dst = &out[0];
+ for( size_t i=0; i<length; i+=1 )
+ dst += sprintf(dst,"%02x", bytes[i]);
+ return [[[NSString alloc] initWithBytes: out length: 2*length encoding: NSASCIIStringEncoding]
+ autorelease];
+
+}
+
+
+- (NSString*)randomStringOfLength: (size_t)length {
+ size_t byteCount = length/2;
+ uint8_t bytes[byteCount];
+ SecRandomCopyBytes(kSecRandomDefault, byteCount, bytes);
+ return [self hexOfBytes: bytes length: byteCount];
+}
+
+
+- (NSString*) hashPassword: (NSString*)password withSalt: (NSString*)salt {
+ // Compute the SHA-1 digest of the password concatenated with the salt:
+ uint8_t digest[CC_SHA1_DIGEST_LENGTH];
+ NSData* d = [[password stringByAppendingString: salt] dataUsingEncoding: NSUTF8StringEncoding];
+ CC_SHA1(d.bytes, d.length, digest);
+ return [self hexOfBytes: digest length: sizeof(digest)];
+}
+
+
+- (BOOL) setupAdminAccount {
+ NSString* path = self.localIniFilePath;
+ NSString* contents = [NSString stringWithContentsOfFile: path encoding: NSUTF8StringEncoding error: nil];
+ if (contents) {
+ NSRange r = [contents rangeOfString: @"\n[admins]\n" kAdminUserName " = "];
+ if (r.length > 0)
+ return YES; // already contains an admin section
+ } else {
+ contents = @"";
+ }
+
+ NSString* password = [self randomStringOfLength: 32];
+ NSString* salt = [self randomStringOfLength: 32];
+ NSString* hashedPassword = [self hashPassword: password withSalt: salt];
+
+ contents = [contents stringByAppendingFormat: @"\n\n[admins]\n%@ = -hashed-%@,%@\n",
+ kAdminUserName, hashedPassword, salt];
+
+ NSError* error = nil;
+ if (![contents writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error: &error]) {
+ NSLog(@"Couchbase: Error writing file '%@': %@", path, error);
+ self.error = error;
+ return NO;
+ }
+
+ NSUserDefaults* dflts = [NSUserDefaults standardUserDefaults];
+ [dflts setObject: password forKey: kAdminPasswordPref];
+ [dflts synchronize]; // make sure the password is saved, else it'll be lost
+
+ return YES;
+}
+
+
+- (NSURLCredential*) adminCredential {
+ NSString* password = [[NSUserDefaults standardUserDefaults] stringForKey: kAdminPasswordPref];
+ if (!password)
+ return nil;
+ return [NSURLCredential
+ credentialWithUser: kAdminUserName
+ password: password
+ persistence: NSURLCredentialPersistenceForSession];
+}
+
+
#pragma mark UTILITIES:
- (BOOL)createDir:(NSString*)dirName {
@@ -464,4 +545,5 @@ - (BOOL)installTemplateNamed:(NSString*)name
}
}
+
@end
View
4 Framework/Resources/default_ios.ini
@@ -50,6 +50,10 @@ ios_driver={couch_ios, start_link, []}
[httpd]
bind_address = 127.0.0.1 ; only local clients: will not accept HTTP connections over WiFi
port = 0 ; means server will pick an available port when it starts
+WWW-Authenticate = Basic realm="Couchbase Mobile" ; triggers client to attempt basic auth
+
+[couch_httpd_auth]
+require_valid_user = true ; Disables anonymous access (read or write)
[replicator]
; set to false to ignore validity of peer certs (insecure and not recommended!)

0 comments on commit bd1f8d0

Please sign in to comment.