Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Implemented CouchUser (_users) and CouchSecurity (_security) support.

CouchUser* user = [_server userWithName:@"Alice"];
user.password = @"secret"; // will be hashed on the server
// set some additional properties
[user setValue:@"Alice" ofProperty:@"firstname"];
[user setValue:@"Wonderland" ofProperty:@"lastname"];
if ([[user save] wait]) NSLog(@"Saved: %@", user);

CouchSecurity* security = [_db security];
security.readerNames = [NSArray arrayWithObjects:@"bob", nil];
security.adminRoles  = [NSArray arrayWithObjects:@"boss", nil];
// add an admin user
[security addObject:user.name forProperty:kSecurityAdminNamesKey];
if ([security update]) NSLog(@"Saved: %@", security);
  • Loading branch information...
commit 7191f9bd4c05cdfba338e7b9ef910ad13db3f1ff 1 parent dc3dea0
@fabien authored
View
4 Couch/CouchCocoa.h
@@ -19,12 +19,14 @@
#import "CouchDesignDocument.h"
#import "CouchDocument.h"
#import "CouchModel.h"
+#import "CouchUser.h"
+#import "CouchSecurity.h"
#import "CouchPersistentReplication.h"
#import "CouchQuery.h"
#import "CouchRevision.h"
#import "CouchServer.h"
#import "CouchTouchDBServer.h"
-
+#import "CouchTouchDBDatabase.h"
/** @mainpage About CouchCocoa
View
6 Couch/CouchDatabase.h
@@ -16,7 +16,7 @@
#import "CouchResource.h"
#import "CouchReplication.h"
@class RESTCache, CouchChangeTracker, CouchDocument, CouchDesignDocument, CouchModelFactory,
- CouchPersistentReplication, CouchQuery, CouchServer;
+ CouchPersistentReplication, CouchQuery, CouchServer, CouchSecurity;
typedef NSString* (^CouchDocumentPathMap)(NSString* documentID);
@@ -166,6 +166,10 @@ typedef NSString* (^CouchDocumentPathMap)(NSString* documentID);
/** All currently configured persistent replications involving this database, as CouchPersistentReplication objects. */
@property (readonly) NSArray* replications;
+#pragma mark SECURITY OBJECT:
+
+- (CouchSecurity*)security;
+
@end
View
6 Couch/CouchDatabase.m
@@ -16,6 +16,7 @@
#import "CouchDatabase.h"
#import "RESTCache.h"
#import "CouchChangeTracker.h"
+#import "CouchSecurity.h"
#import "CouchInternal.h"
@@ -357,7 +358,12 @@ - (NSArray*) replicateWithURL: (NSURL*)targetURL exclusively: (BOOL)exclusively
return repls;
}
+#pragma mark -
+#pragma mark SECURITY OBJECT:
+- (CouchSecurity*)security {
+ return [[CouchSecurity alloc] initWithDatabase:self];
+}
#pragma mark -
#pragma mark TRACKING CHANGES:
View
9 Couch/CouchInternal.h
@@ -84,12 +84,21 @@ typedef void (^OnDatabaseChangeBlock)(CouchDocument*, BOOL externalChange);
@end
+@interface CouchDynamicObject ()
+- (void) updateProperties:(NSDictionary*)properties strict:(BOOL)strict;
+@end
+
+@interface CouchSecurity ()
+- (id) initWithDatabase: (CouchDatabase*)database;
+@end
+
@interface CouchServer ()
@property (readonly) BOOL isEmbeddedServer;
- (CouchPersistentReplication*) replicationWithSource: (NSString*)source
target: (NSString*)target;
- (void) registerActiveTask: (NSDictionary*)activeTask;
+- (Class) userModelClass;
@end
View
3  Couch/CouchResource.h
@@ -27,4 +27,7 @@ extern NSString* const kCouchDBErrorDomain;
/** The owning database. */
@property (readonly) CouchDatabase* database;
+/** Performs a HEAD request to check if the resource exists. */
+- (BOOL) exists;
+
@end
View
3  Couch/CouchResource.m
@@ -28,6 +28,9 @@ - (CouchDatabase*) database {
// No, this is not an infinite regress. CouchDatabase overrides this to return self.
}
+- (BOOL) exists {
+ return (BOOL)[[self HEAD] wait];
+}
- (NSError*) operation: (RESTOperation*)op willCompleteWithError: (NSError*)error {
error = [super operation: op willCompleteWithError: error];
View
51 Couch/CouchSecurity.h
@@ -0,0 +1,51 @@
+//
+// CouchSecurity.h
+// CouchCocoa
+//
+// Created by Fabien Franzen on 20-06-12.
+// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
+//
+// For more info: http://wiki.apache.org/couchdb/Security_Features_Overview
+//
+
+#import "CouchDynamicObject.h"
+
+
+static NSString* const kSecurityAdminNamesKey = @"adminNames";
+static NSString* const kSecurityAdminRolesKey = @"adminRoles";
+
+static NSString* const kSecurityReaderNamesKey = @"readerNames";
+static NSString* const kSecurityReaderRolesKey = @"readerRoles";
+
+@class CouchUser, CouchResource, RESTOperation;
+
+@interface CouchSecurity : CouchDynamicObject
+{
+ CouchResource* _resource;
+ NSMutableDictionary* _properties;
+}
+
+@property (readonly) NSDictionary* properties;
+
+/** Admins: can create/update Design Documents and manipulate the (per db) Couch Security Object.
+ However, they cannot create or delete a database. **/
+@property (copy,readwrite) NSArray* adminNames;
+@property (copy,readwrite) NSArray* adminRoles;
+
+/** Readers: can read and also create//update/delete documents (when validation permits),
+ but are not allowed to create/update Design Documents. **/
+@property (copy,readwrite) NSArray* readerNames;
+@property (copy,readwrite) NSArray* readerRoles;
+
+/** The dropbox value is supported on Refuge and rcouch:
+ https://github.com/refuge/couch_core/commit/742846156eb5b881f88b88c6deecbef4e66ed2a0 **/
+@property (readwrite) BOOL dropBox;
+
+/** Save the current settings to the database. **/
+- (RESTOperation*)update;
+
+/** Helper methods to work with the nested collections of names and roles. **/
+- (void)addObject:(id)obj forProperty:(NSString *)property;
+- (void)removeObject:(id)obj forProperty:(NSString *)property;
+
+@end
View
117 Couch/CouchSecurity.m
@@ -0,0 +1,117 @@
+//
+// CouchSecurity.m
+// CouchCocoa
+//
+// Created by Fabien Franzen on 20-06-12.
+// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchSecurity.h"
+#import "CouchUser.h"
+#import "CouchInternal.h"
+
+@interface CouchSecurity ()
+- (BOOL)load;
+@end
+
+@implementation CouchSecurity
+
+- (id)initWithDatabase: (CouchDatabase*)database {
+ self = [super init];
+ if(self) {
+ _resource = [[CouchResource alloc] initWithParent:database relativePath: @"_security"];
+ [_resource retain];
+ }
+ return self;
+}
+
+@dynamic adminNames, adminRoles, readerNames, readerRoles, dropBox;
+
+- (void)dealloc {
+ [_resource release];
+ [_properties release];
+ [super dealloc];
+}
+
+- (NSString*)keyPathForProperty:(NSString*)key {
+ static NSDictionary* mapping = nil;
+ if (mapping == nil) {
+ mapping = [NSDictionary dictionaryWithObjectsAndKeys:
+ @"admins.names", kSecurityAdminNamesKey,
+ @"admins.roles", kSecurityAdminRolesKey,
+ @"readers.names", kSecurityReaderNamesKey,
+ @"readers.roles", kSecurityReaderRolesKey,
+ nil];
+ }
+ return [mapping objectForKey:key];
+}
+
+- (id) getValueOfProperty: (NSString*)property {
+ NSString* keyPath = [self keyPathForProperty:property];
+ if (!keyPath) keyPath = property;
+ return [self.properties valueForKeyPath:keyPath];
+}
+
+- (BOOL) setValue: (id)value ofProperty: (NSString*)property {
+ NSString* keyPath = [self keyPathForProperty:property];
+ if (!keyPath) keyPath = property;
+ [self.properties setValue:value forKeyPath:keyPath];
+ return YES;
+}
+
+- (void)addObject:(id)obj forProperty:(NSString *)property {
+ id prop = [self getValueOfProperty:property];
+ if ([prop respondsToSelector:@selector(addObject:)]) {
+ if (![prop containsObject:obj]) [prop addObject:obj];
+ }
+}
+
+- (void)removeObject:(id)obj forProperty:(NSString *)property {
+ id prop = [self getValueOfProperty:property];
+ if ([prop respondsToSelector:@selector(removeObject:)]) {
+ [prop removeObject:obj];
+ }
+}
+
+- (NSDictionary *)properties {
+ if (!_properties) {
+ _properties = [[NSMutableDictionary dictionary] retain];
+ [_properties setValue:[NSMutableDictionary dictionary] forKey:@"admins"];
+ [_properties setValue:[NSMutableDictionary dictionary] forKey:@"readers"];
+ [_properties setValue:[NSMutableArray array] forKeyPath:@"admins.names"];
+ [_properties setValue:[NSMutableArray array] forKeyPath:@"admins.roles"];
+ [_properties setValue:[NSMutableArray array] forKeyPath:@"readers.names"];
+ [_properties setValue:[NSMutableArray array] forKeyPath:@"readers.roles"];
+ [self load];
+ }
+ return _properties;
+}
+
+- (RESTOperation*)update {
+ return [_resource PUTJSON: self.properties parameters: nil];
+}
+
+- (BOOL)load {
+ RESTOperation* op = [_resource GET];
+ if ([op wait]) {
+ NSDictionary* response = $castIf(NSDictionary, op.responseBody.fromJSON);
+ if (response && response.count > 0) {
+ NSSet *writableNames = [self.class writablePropertyNames];
+ [writableNames enumerateObjectsUsingBlock:^(id key, BOOL *stop) {
+ // all mapped keyPaths refer to NSMutableArrays
+ NSString* keyPath = [self keyPathForProperty:key];
+ BOOL isMutable = (BOOL)keyPath;
+ if (!keyPath) keyPath = key;
+ id value = [response valueForKeyPath:keyPath];
+ if (isMutable && [value respondsToSelector:@selector(mutableCopy)]) {
+ value = [[value mutableCopy] autorelease];
+ }
+ [self.properties setValue:value forKeyPath:keyPath];
+ }];
+ }
+ return YES;
+ }
+ return NO;
+}
+
+@end
View
18 Couch/CouchServer.h
@@ -14,7 +14,7 @@
// and limitations under the License.
#import "CouchResource.h"
-@class CouchDatabase, CouchLiveQuery, CouchPersistentReplication, RESTCache;
+@class CouchDatabase, CouchLiveQuery, CouchPersistentReplication, CouchUser, RESTCache;
/** The top level of a CouchDB server. Contains CouchDatabases. */
@@ -28,6 +28,7 @@
RESTOperation* _activeTasksOp;
NSTimer* _activityPollTimer;
CouchLiveQuery* _replicationsQuery;
+ CouchLiveQuery* _usersQuery;
}
/** Initialize given a server URL. */
@@ -66,10 +67,25 @@
#pragma mark - REPLICATION:
+/** This is the server-wide _replicators database **/
+@property (readonly) CouchDatabase* replicatorDatabase;
+
/** All currently defined CouchPersistentReplications (as stored in the replicator database.)
To create a replication, use the methods on CouchDatabase. */
@property (readonly) NSArray* replications;
+#pragma mark - USERS:
+
+/** This is the server-wide _users database **/
+@property (readonly) CouchDatabase* usersDatabase;
+
+/** All currently defined CouchUsers (as stored in the _users database.)
+ To create a user, use userWithName and the CouchUser methods. */
+@property (readonly) NSArray* users;
+
+/** Will get a CouchUser with the correct document ID for the given name. **/
+- (CouchUser*)userWithName:(NSString*)name;
+
@end
View
45 Couch/CouchServer.m
@@ -14,6 +14,7 @@
// and limitations under the License.
#import "CouchServer.h"
+#import "CouchUser.h"
#import "CouchInternal.h"
#import "RESTCache.h"
@@ -48,6 +49,7 @@ - (void)dealloc {
[_activeTasks release];
[_activityRsrc release];
[_replicationsQuery release];
+ [_usersQuery release];
[_dbCache release];
[super dealloc];
}
@@ -56,6 +58,8 @@ - (void)dealloc {
- (void) close {
[_replicationsQuery release];
_replicationsQuery = nil;
+ [_usersQuery release];
+ _usersQuery = nil;
for (CouchDatabase* db in _dbCache.allCachedResources)
[db unretainDocumentCache];
}
@@ -163,6 +167,47 @@ - (CouchPersistentReplication*) replicationWithSource: (NSString*)source
source: source target: target];
}
+#pragma mark USERS DATABASE:
+
+
+- (CouchUser*)userWithName:(NSString*)name {
+ NSString *userID = [[self userModelClass] userIDWithName:name];
+ CouchDatabase* db = [self usersDatabase];
+ CouchDocument* doc = [db documentWithID: userID];
+ return [[self userModelClass] modelForDocument:doc];
+}
+
+
+- (CouchDatabase*)usersDatabase {
+ return [self databaseNamed:@"_users"];
+}
+
+
+- (CouchLiveQuery*) usersQuery {
+ if (!_usersQuery) {
+ CouchDatabase* usersDB = [self usersDatabase];
+ usersDB.tracksChanges = YES;
+ _usersQuery = [[[usersDB getAllDocuments] asLiveQuery] retain];
+ [_usersQuery wait];
+ }
+ return _usersQuery;
+}
+
+
+- (NSArray*) users {
+ return [self.usersQuery.rows.allObjects rest_map: ^(id row) {
+ NSString* docID = [row documentID];
+ if ([docID hasPrefix: @"_design/"] || [docID hasPrefix: @"_local/"])
+ return (id)nil;
+ return [CouchUser modelForDocument: [row document]];
+ }];
+}
+
+
+- (Class) userModelClass {
+ return [CouchUser class];
+}
+
#pragma mark - ACTIVITY MONITOR:
View
31 Couch/CouchUser.h
@@ -0,0 +1,31 @@
+//
+// CouchUser.h
+// CouchCocoa
+//
+// Created by Fabien Franzen on 20-06-12.
+// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
+//
+// For more info: http://wiki.apache.org/couchdb/Security_Features_Overview
+//
+
+#import "CouchModel.h"
+
+@interface CouchUser : CouchModel
+
+/** Type is always 'user'. **/
+@property (readonly) NSString* type;
+
+/** Name is a lowercase string, also part of ID: org.couchdb.user:<name>. **/
+@property (readonly) NSString* name;
+
+/** Roles are the roles this user has. Defaults to empty array. **/
+@property (copy) NSArray* roles;
+
+/** This returns a CouchUser - does not make any server calls until needed. **/
++ (NSString*) userIDWithName:(NSString*)name;
+
+/** The password value will be removed from the document once it's hashed on the server.
+ It's not mandatory when creating a new CouchUser. Requires CouchDB >= 1.2.0. **/
+- (void) setPassword:(NSString*)password;
+
+@end
View
36 Couch/CouchUser.m
@@ -0,0 +1,36 @@
+//
+// CouchUser.m
+// CouchCocoa
+//
+// Created by Fabien Franzen on 20-06-12.
+// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchUser.h"
+#import "CouchInternal.h"
+
+static NSString* const kUserIDPrefix = @"org.couchdb.user:";
+static NSUInteger const kUserIDPrefixLength = 17;
+
+@implementation CouchUser
+
+@dynamic type, name, roles;
+
++ (NSString*) userIDWithName:(NSString*)name {
+ NSString* formatted = [name lowercaseString];
+ return [NSString stringWithFormat:@"%@%@", kUserIDPrefix, formatted];
+}
+
+- (void) setPassword:(NSString*)password {
+ [self setValue:password ofProperty:@"password"];
+}
+
+- (BOOL) setDefaultValues {
+ NSString* name = [[[self document] documentID] substringFromIndex:kUserIDPrefixLength];
+ [self setDefault:@"user" ofProperty:@"type"];
+ [self setDefault:name ofProperty:@"name"];
+ [self setDefault:[NSArray array] ofProperty:@"roles"];
+ return YES;
+}
+
+@end
View
70 CouchCocoa.xcodeproj/project.pbxproj
@@ -48,7 +48,6 @@
279276F114215D5600002958 /* RESTBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = 279276ED14215D5600002958 /* RESTBase64.m */; };
27938C61140C01D200117675 /* CouchDynamicObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 27DB82201408202E00E57444 /* CouchDynamicObject.h */; settings = {ATTRIBUTES = (Public, ); }; };
27938C63140C01DC00117675 /* CouchModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 27DB82231408225300E57444 /* CouchModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 2795994F140A02DB001C168A /* Test_Model.m in Sources */ = {isa = PBXBuildFile; fileRef = 2795994E140A02DB001C168A /* Test_Model.m */; };
27959950140A02DB001C168A /* Test_Model.m in Sources */ = {isa = PBXBuildFile; fileRef = 2795994E140A02DB001C168A /* Test_Model.m */; };
279906D2149930DA003D4338 /* CouchConnectionChangeTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = 279906CE149930DA003D4338 /* CouchConnectionChangeTracker.h */; };
279906D3149930DA003D4338 /* CouchConnectionChangeTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = 279906CE149930DA003D4338 /* CouchConnectionChangeTracker.h */; };
@@ -58,12 +57,11 @@
279906D7149930DA003D4338 /* CouchSocketChangeTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = 279906D0149930DA003D4338 /* CouchSocketChangeTracker.h */; };
279906D8149930DA003D4338 /* CouchSocketChangeTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 279906D1149930DA003D4338 /* CouchSocketChangeTracker.m */; };
279906D9149930DA003D4338 /* CouchSocketChangeTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 279906D1149930DA003D4338 /* CouchSocketChangeTracker.m */; };
- 279CA782156FE4B700871563 /* CouchTouchDBDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 279CA780156FE4B700871563 /* CouchTouchDBDatabase.h */; };
- 279CA783156FE4B700871563 /* CouchTouchDBDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 279CA780156FE4B700871563 /* CouchTouchDBDatabase.h */; };
+ 279CA782156FE4B700871563 /* CouchTouchDBDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 279CA780156FE4B700871563 /* CouchTouchDBDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 279CA783156FE4B700871563 /* CouchTouchDBDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 279CA780156FE4B700871563 /* CouchTouchDBDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; };
279CA784156FE4B700871563 /* CouchTouchDBDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 279CA781156FE4B700871563 /* CouchTouchDBDatabase.m */; };
279CA785156FE4B700871563 /* CouchTouchDBDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 279CA781156FE4B700871563 /* CouchTouchDBDatabase.m */; };
279CCBF213F9823F00C38C82 /* CouchReplication.h in Headers */ = {isa = PBXBuildFile; fileRef = 279CCBF013F9823F00C38C82 /* CouchReplication.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 279CCBF313F9823F00C38C82 /* CouchReplication.h in Headers */ = {isa = PBXBuildFile; fileRef = 279CCBF013F9823F00C38C82 /* CouchReplication.h */; };
279CCBF413F9823F00C38C82 /* CouchReplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 279CCBF113F9823F00C38C82 /* CouchReplication.m */; };
279CCBF513F9823F00C38C82 /* CouchReplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 279CCBF113F9823F00C38C82 /* CouchReplication.m */; };
279CCBF613F9955900C38C82 /* CouchReplication.h in Headers */ = {isa = PBXBuildFile; fileRef = 279CCBF013F9823F00C38C82 /* CouchReplication.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -130,10 +128,8 @@
27DB822F14084BE900E57444 /* AddressCard.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DB822E14084BE900E57444 /* AddressCard.m */; };
27DB823214084F1900E57444 /* ShoppingItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DB823114084F1800E57444 /* ShoppingItem.m */; };
27E4DD0C141921E000A3D8F6 /* CouchPersistentReplication.h in Headers */ = {isa = PBXBuildFile; fileRef = 27E4DD0A141921E000A3D8F6 /* CouchPersistentReplication.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 27E4DD0D141921E000A3D8F6 /* CouchPersistentReplication.h in Headers */ = {isa = PBXBuildFile; fileRef = 27E4DD0A141921E000A3D8F6 /* CouchPersistentReplication.h */; };
27E4DD0E141921E000A3D8F6 /* CouchPersistentReplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E4DD0B141921E000A3D8F6 /* CouchPersistentReplication.m */; };
27E4DD0F141921E000A3D8F6 /* CouchPersistentReplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E4DD0B141921E000A3D8F6 /* CouchPersistentReplication.m */; };
- 27E4DD14141959EB00A3D8F6 /* CouchPersistentReplication.h in Headers */ = {isa = PBXBuildFile; fileRef = 27E4DD0A141921E000A3D8F6 /* CouchPersistentReplication.h */; settings = {ATTRIBUTES = (Public, ); }; };
27E9C61914A0EECC00F67966 /* CouchTouchDBServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 27E9C61714A0EECC00F67966 /* CouchTouchDBServer.h */; settings = {ATTRIBUTES = (Public, ); }; };
27E9C61A14A0EECC00F67966 /* CouchTouchDBServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 27E9C61714A0EECC00F67966 /* CouchTouchDBServer.h */; };
27E9C61B14A0EECC00F67966 /* CouchTouchDBServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E9C61814A0EECC00F67966 /* CouchTouchDBServer.m */; };
@@ -156,9 +152,21 @@
27EEA5B613D6052300D7ACA4 /* REST.h in Headers */ = {isa = PBXBuildFile; fileRef = 270A664413A5BA4600791F4A /* REST.h */; settings = {ATTRIBUTES = (Public, ); }; };
27EF148C1396D8CC0052913E /* DemoAppController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF148B1396D8CC0052913E /* DemoAppController.m */; };
27EF14B31396DD3B0052913E /* AddressesDemo.xib in Resources */ = {isa = PBXBuildFile; fileRef = 27EF14B21396DD3B0052913E /* AddressesDemo.xib */; };
- 5FDC4FC015932E58004B0576 /* Test_REST.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E9D9313A2EBE0009F18E9 /* Test_REST.m */; };
- 5FDC4FC115932E5A004B0576 /* Test_Couch.m in Sources */ = {isa = PBXBuildFile; fileRef = 270A663A13A5B36900791F4A /* Test_Couch.m */; };
- 5FDC4FC215932E5D004B0576 /* Test_DynamicObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 27911B701411A7C100ABD31B /* Test_DynamicObject.m */; };
+ 5FDC4FC51593306F004B0576 /* CouchSecurity.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FDC4FC31593306E004B0576 /* CouchSecurity.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 5FDC4FC71593306F004B0576 /* CouchSecurity.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDC4FC41593306F004B0576 /* CouchSecurity.m */; };
+ 5FDC4FC81593306F004B0576 /* CouchSecurity.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDC4FC41593306F004B0576 /* CouchSecurity.m */; };
+ 5FDC4FC91593308E004B0576 /* CouchPersistentReplication.h in Headers */ = {isa = PBXBuildFile; fileRef = 27E4DD0A141921E000A3D8F6 /* CouchPersistentReplication.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 5FDC4FCE15933162004B0576 /* Test_Security.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDC4FCC15933161004B0576 /* Test_Security.m */; };
+ 5FDC4FF3159336EC004B0576 /* CouchUser.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FDC4FF1159336EB004B0576 /* CouchUser.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 5FDC4FF5159336EC004B0576 /* CouchUser.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDC4FF2159336EC004B0576 /* CouchUser.m */; };
+ 5FDC4FF6159336EC004B0576 /* CouchUser.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDC4FF2159336EC004B0576 /* CouchUser.m */; };
+ 5FDC4FF815934D32004B0576 /* Test_Model.m in Sources */ = {isa = PBXBuildFile; fileRef = 2795994E140A02DB001C168A /* Test_Model.m */; };
+ 5FDC500215939368004B0576 /* CouchUser.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FDC4FF1159336EB004B0576 /* CouchUser.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 5FDC50031593936C004B0576 /* CouchSecurity.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FDC4FC31593306E004B0576 /* CouchSecurity.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 5FDC50481593B966004B0576 /* Test_REST.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E9D9313A2EBE0009F18E9 /* Test_REST.m */; };
+ 5FDC50491593B968004B0576 /* Test_Couch.m in Sources */ = {isa = PBXBuildFile; fileRef = 270A663A13A5B36900791F4A /* Test_Couch.m */; };
+ 5FDC504A1593B96B004B0576 /* Test_DynamicObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 27911B701411A7C100ABD31B /* Test_DynamicObject.m */; };
+ 5FDC504B1593B96F004B0576 /* Test_Security.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDC4FCC15933161004B0576 /* Test_Security.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -237,7 +245,7 @@
27853EF413DF6F5E00478EBB /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = usr/lib/libcrypto.dylib; sourceTree = SDKROOT; };
278B22F1138F1F5F00DDD950 /* CouchServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchServer.h; sourceTree = "<group>"; };
278B22F2138F1F5F00DDD950 /* CouchServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CouchServer.m; sourceTree = "<group>"; };
- 278B22F4138F269200DDD950 /* CouchDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchDatabase.h; sourceTree = "<group>"; };
+ 278B22F4138F269200DDD950 /* CouchDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = CouchDatabase.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
278B22F5138F269200DDD950 /* CouchDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = CouchDatabase.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
278B22F7138F2AA600DDD950 /* CouchDocument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchDocument.h; sourceTree = "<group>"; };
278B22F8138F2AA600DDD950 /* CouchDocument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CouchDocument.m; sourceTree = "<group>"; };
@@ -309,6 +317,11 @@
27EF148B1396D8CC0052913E /* DemoAppController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoAppController.m; sourceTree = "<group>"; };
27EF14B21396DD3B0052913E /* AddressesDemo.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AddressesDemo.xib; sourceTree = "<group>"; };
27EFB7BD13CF66FD00FA1485 /* ShoppingDemo-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "ShoppingDemo-Info.plist"; path = "Demo/ShoppingDemo-Info.plist"; sourceTree = SOURCE_ROOT; };
+ 5FDC4FC31593306E004B0576 /* CouchSecurity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchSecurity.h; sourceTree = "<group>"; };
+ 5FDC4FC41593306F004B0576 /* CouchSecurity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CouchSecurity.m; sourceTree = "<group>"; };
+ 5FDC4FCC15933161004B0576 /* Test_Security.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = Test_Security.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
+ 5FDC4FF1159336EB004B0576 /* CouchUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchUser.h; sourceTree = "<group>"; };
+ 5FDC4FF2159336EC004B0576 /* CouchUser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CouchUser.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -402,6 +415,7 @@
08FB7795FE84155DC02AAC07 /* Couch */ = {
isa = PBXGroup;
children = (
+ 270A663C13A5B3DF00791F4A /* CouchCocoa.h */,
278B22F1138F1F5F00DDD950 /* CouchServer.h */,
278B22F2138F1F5F00DDD950 /* CouchServer.m */,
27E9C61714A0EECC00F67966 /* CouchTouchDBServer.h */,
@@ -430,7 +444,7 @@
279CCBF113F9823F00C38C82 /* CouchReplication.m */,
27E4DD0A141921E000A3D8F6 /* CouchPersistentReplication.h */,
27E4DD0B141921E000A3D8F6 /* CouchPersistentReplication.m */,
- 270A663C13A5B3DF00791F4A /* CouchCocoa.h */,
+ 5FDC4FF715933703004B0576 /* Security */,
279906DB149930E3003D4338 /* ChangeTracker */,
27333BCB13B7E70100EF5A10 /* Internal */,
);
@@ -474,6 +488,7 @@
270A663A13A5B36900791F4A /* Test_Couch.m */,
27911B701411A7C100ABD31B /* Test_DynamicObject.m */,
2795994E140A02DB001C168A /* Test_Model.m */,
+ 5FDC4FCC15933161004B0576 /* Test_Security.m */,
27911B731411A8C700ABD31B /* CouchTestCase.h */,
27911B741411A8C700ABD31B /* CouchTestCase.m */,
2771C7C11472ECF70012DF57 /* logo.png */,
@@ -608,6 +623,17 @@
path = Demo;
sourceTree = "<group>";
};
+ 5FDC4FF715933703004B0576 /* Security */ = {
+ isa = PBXGroup;
+ children = (
+ 5FDC4FF1159336EB004B0576 /* CouchUser.h */,
+ 5FDC4FF2159336EC004B0576 /* CouchUser.m */,
+ 5FDC4FC31593306E004B0576 /* CouchSecurity.h */,
+ 5FDC4FC41593306F004B0576 /* CouchSecurity.m */,
+ );
+ name = Security;
+ sourceTree = "<group>";
+ };
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@@ -615,10 +641,8 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
- 279CCBF313F9823F00C38C82 /* CouchReplication.h in Headers */,
27DB82221408202E00E57444 /* CouchDynamicObject.h in Headers */,
27DB82261408225300E57444 /* CouchModel.h in Headers */,
- 27E4DD0D141921E000A3D8F6 /* CouchPersistentReplication.h in Headers */,
279276EF14215D5600002958 /* RESTBase64.h in Headers */,
27CB654D143A746700EEA1F2 /* CouchDesignDocument_Embedded.h in Headers */,
27D083B9143FBEEA0067702F /* CouchbaseCallbacks.h in Headers */,
@@ -661,6 +685,8 @@
279906D2149930DA003D4338 /* CouchConnectionChangeTracker.h in Headers */,
279906D6149930DA003D4338 /* CouchSocketChangeTracker.h in Headers */,
279CA782156FE4B700871563 /* CouchTouchDBDatabase.h in Headers */,
+ 5FDC4FC51593306F004B0576 /* CouchSecurity.h in Headers */,
+ 5FDC4FF3159336EC004B0576 /* CouchUser.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -685,11 +711,13 @@
27E9C61E14A0F6A300F67966 /* CouchTouchDBServer.h in Headers */,
27EEA5AE13D6050C00D7ACA4 /* CouchDatabase.h in Headers */,
279CCBF613F9955900C38C82 /* CouchReplication.h in Headers */,
- 27E4DD14141959EB00A3D8F6 /* CouchPersistentReplication.h in Headers */,
27C7280013EB238900C7ADF5 /* CouchUITableSource.h in Headers */,
27938C61140C01D200117675 /* CouchDynamicObject.h in Headers */,
27938C63140C01DC00117675 /* CouchModel.h in Headers */,
279CE39214D1F761009F3FA6 /* CouchModelFactory.h in Headers */,
+ 5FDC4FC91593308E004B0576 /* CouchPersistentReplication.h in Headers */,
+ 5FDC500215939368004B0576 /* CouchUser.h in Headers */,
+ 5FDC50031593936C004B0576 /* CouchSecurity.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1014,6 +1042,8 @@
27AE23B0147C95D3005AAB52 /* CouchModelFactory.m in Sources */,
2783A0C7156D616800DC8692 /* CouchEmbeddedServer.m in Sources */,
279CA785156FE4B700871563 /* CouchTouchDBDatabase.m in Sources */,
+ 5FDC4FC81593306F004B0576 /* CouchSecurity.m in Sources */,
+ 5FDC4FF6159336EC004B0576 /* CouchUser.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1026,6 +1056,7 @@
27959950140A02DB001C168A /* Test_Model.m in Sources */,
27911B721411A7C100ABD31B /* Test_DynamicObject.m in Sources */,
27911B761411A8C700ABD31B /* CouchTestCase.m in Sources */,
+ 5FDC4FCE15933162004B0576 /* Test_Security.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1068,6 +1099,8 @@
27E9C61B14A0EECC00F67966 /* CouchTouchDBServer.m in Sources */,
27AE23AF147C95D3005AAB52 /* CouchModelFactory.m in Sources */,
279CA784156FE4B700871563 /* CouchTouchDBDatabase.m in Sources */,
+ 5FDC4FC71593306F004B0576 /* CouchSecurity.m in Sources */,
+ 5FDC4FF5159336EC004B0576 /* CouchUser.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1075,11 +1108,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 2795994F140A02DB001C168A /* Test_Model.m in Sources */,
27911B751411A8C700ABD31B /* CouchTestCase.m in Sources */,
- 5FDC4FC015932E58004B0576 /* Test_REST.m in Sources */,
- 5FDC4FC115932E5A004B0576 /* Test_Couch.m in Sources */,
- 5FDC4FC215932E5D004B0576 /* Test_DynamicObject.m in Sources */,
+ 5FDC4FF815934D32004B0576 /* Test_Model.m in Sources */,
+ 5FDC50481593B966004B0576 /* Test_REST.m in Sources */,
+ 5FDC50491593B968004B0576 /* Test_Couch.m in Sources */,
+ 5FDC504A1593B96B004B0576 /* Test_DynamicObject.m in Sources */,
+ 5FDC504B1593B96F004B0576 /* Test_Security.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
6 Model/CouchDynamicObject.m
@@ -25,6 +25,12 @@ - (BOOL) setValue: (id)value ofProperty: (NSString*)property {
return NO;
}
+- (void) updateProperties:(NSDictionary*)properties strict:(BOOL)strict {
+ NSSet *writableNames = [self.class writablePropertyNames];
+ [properties enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
+ if (!strict || [writableNames member:key]) [self setValue:value ofProperty:key];
+ }];
+}
#pragma mark - SELECTOR-TO-PROPERTY NAME MAPPING:
View
20 Model/CouchModel.h
@@ -14,7 +14,7 @@
There's a 1::1 mapping between these and CouchDocuments; call +modelForDocument: to get (or create) a model object for a document, and .document to get the document of a model.
You should subclass this and declare properties in the subclass's @@interface. As with NSManagedObject, you don't need to implement their accessor methods or declare instance variables; simply note them as '@@dynamic' in the class @@implementation. The property value will automatically be fetched from or stored to the document, using the same name.
Supported scalar types are bool, char, short, int, double. These map to JSON numbers, except 'bool' which maps to JSON 'true' and 'false'. (Use bool instead of BOOL.)
- Supported object types are NSString, NSNumber, NSData, NSDate, NSArray, NSDictionary. (NSData and NSDate are not native JSON; they will be automatically converted to/from strings in base64 and ISO date formats, respectively.)
+ Supported object types are NSString, NSNumber, NSData, NSDate, NSURL, NSArray, NSDictionary. (NSData, NSDate and NSURL are not native JSON; they will be automatically converted to/from strings in base64 and ISO date formats, respectively.)
Additionally, a property's type can be a pointer to a CouchModel subclass. This provides references between model objects. The raw property value in the document must be a string whose value is interpreted as a document ID. */
@interface CouchModel : CouchDynamicObject
{
@@ -44,10 +44,13 @@
(This method is mostly here so that NSController objects can create CouchModels.) */
- (id) init;
-/** Attempts to load the document/revision (properties) if not already loaded **/
+/** Performs a synchronous HEAD request to check if the associated document exists. **/
+- (BOOL) exists;
+
+/** Attempts to load the document/revision (properties) from the database if not already loaded. **/
- (BOOL) load;
-/** Force reloading of the document/revision (properties) **/
+/** Force reloading of the document/revision (properties) from the database. **/
- (BOOL) reload;
/** The document this item is associated with. Will be nil if it's new and unsaved. */
@@ -97,6 +100,7 @@
#pragma mark - PROPERTIES & ATTACHMENTS:
+/** A dictionary containing all known properties; excludes those not explicitly defined. **/
- (NSDictionary*) properties;
/** Replace the current properties dictionary completely, taking default values into account. **/
@@ -105,7 +109,7 @@
/** Reset the current properties dictionary completely, while keeping default values. **/
- (void) clearProperties;
-/** Merge the current properties dictionary; writable properties only. **/
+/** Merge with the current properties dictionary; writable properties only. **/
- (void) updateProperties:(NSDictionary*)properties;
/** Reset known (writable) properties to default values. **/
@@ -119,6 +123,9 @@
You can use this for document properties that you haven't added @@property declarations for. */
- (BOOL) setValue: (id)value ofProperty: (NSString*)property;
+/** Sets the default value of a property by name.
+ The value will only be set if the current value is nil. */
+- (BOOL) setDefault: (id)value ofProperty: (NSString*)property;
/** The names of all attachments (array of strings).
This reflects unsaved changes made by creating or deleting attachments. */
@@ -153,8 +160,9 @@
- (NSString*) idForNewDocumentInDatabase: (CouchDatabase*)db;
/** Called when the model's properties are reset or the document is explicitly loaded, but missing.
- If it's missing, it's a new document, ready to be initialized with the defaults. */
-- (void) setDefaultValues;
+ If it's missing, it's a new document, ready to be initialized with the defaults.
+ Return YES to mark the object for saving just with the defaults. */
+- (BOOL) setDefaultValues;
/** Called when the model's properties are reloaded from the document.
This happens both when initialized from a document, and after an external change. */
View
70 Model/CouchModel.m
@@ -21,21 +21,26 @@ - (void) reset;
@implementation CouchModel
-- (BOOL)ensureDefaults {
- if (self.document.currentRevision == nil) {
- [self setDefaultValues];
- _needsSave = NO;
- return YES;
- }
+
+- (BOOL) setDefault: (id)value ofProperty: (NSString*)property {
+ id current = [self getValueOfProperty:property];
+ if (current == nil)
+ return [self setValue:value ofProperty:property];
return NO;
}
+
+- (void)ensureDefaults {
+ _needsSave = [self setDefaultValues];
+}
+
+
- (id)init {
return [self initWithDocument: nil];
}
-- (id) initWithDocument: (CouchDocument*)document
-{
+
+- (id) initWithDocument: (CouchDocument*)document {
self = [super init];
if (self) {
if (document) {
@@ -99,14 +104,23 @@ - (NSString*) description {
#pragma mark - DOCUMENT / DATABASE:
+
- (BOOL) load {
- if (!self.document.currentRevisionID) { // not loaded yet
- if (self.document.currentRevision) [self couchDocumentChanged: self.document];
- return [self ensureDefaults];
+ if (!self.document.currentRevision) { // not loaded yet, but attempt to
+ [self couchDocumentChanged: self.document];
+ [self ensureDefaults];
+ [self didLoadFromDocument];
+ return YES;
}
return NO;
}
+
+- (BOOL) exists {
+ return self.document.exists;
+}
+
+
- (void) reset {
_properties = nil;
_changedNames = nil;
@@ -115,11 +129,13 @@ - (void) reset {
_needsSave = NO;
}
+
- (BOOL) reload {
[self reset];
return [self load];
}
+
- (CouchDocument*) document {
return _document;
}
@@ -176,8 +192,9 @@ - (RESTOperation*) deleteDocument {
return op;
}
-- (void) setDefaultValues {
+- (BOOL) setDefaultValues {
// subclasses can override this
+ return NO; // return YES to mark for saving
}
- (void) didLoadFromDocument {
@@ -341,13 +358,6 @@ - (void) updateProperties:(NSDictionary*)properties {
[self updateProperties:properties strict:YES];
}
-- (void) updateProperties:(NSDictionary*)properties strict:(BOOL)strict {
- NSSet *writableNames = [self.class writablePropertyNames];
- [properties enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
- if (!strict || [writableNames member:key]) [self setValue:value ofProperty:key];
- }];
-}
-
- (void) clearProperties {
_properties = nil;
[self setDefaultValues];
@@ -366,6 +376,8 @@ - (id) externalizePropertyValue: (id)value {
value = [RESTBody base64WithData: value];
else if ([value isKindOfClass: [NSDate class]])
value = [RESTBody JSONObjectWithDate: value];
+ else if ([value isKindOfClass: [NSURL class]])
+ value = [RESTBody JSONObjectWithURL: value];
return value;
}
@@ -447,6 +459,20 @@ - (NSDate*) getDateProperty: (NSString*)property {
return value;
}
+- (NSURL*) getURLProperty: (NSString*)property {
+ NSURL* value = [_properties objectForKey: property];
+ if (!value) {
+ id rawValue = [_document propertyForKey: property];
+ if ([rawValue isKindOfClass: [NSString class]])
+ value = [RESTBody urlWithJSONObject: rawValue];
+ if (value)
+ [self cacheValue: value ofProperty: property changed: NO];
+ else if (rawValue)
+ Warn(@"Unable to decode date from property %@ of %@", property, _document);
+ }
+ return value;
+}
+
- (CouchDatabase*) databaseForModelProperty: (NSString*)property {
// This is a hook for subclasses to override if they need to, i.e. if the property
// refers to a document in a different database.
@@ -509,6 +535,10 @@ static id getDateProperty(CouchModel *self, SEL _cmd) {
return [self getDateProperty: getterKey(_cmd)];
}
+static id getURLProperty(CouchModel *self, SEL _cmd) {
+ return [self getURLProperty: getterKey(_cmd)];
+}
+
static id getModelProperty(CouchModel *self, SEL _cmd) {
return [self getModelProperty: getterKey(_cmd)];
}
@@ -527,6 +557,8 @@ + (IMP) impForGetterOfClass: (Class)propertyClass {
return (IMP)getDataProperty;
else if (propertyClass == [NSDate class])
return (IMP)getDateProperty;
+ else if (propertyClass == [NSURL class])
+ return (IMP)getURLProperty;
else if ([propertyClass isSubclassOfClass: [CouchModel class]])
return (IMP)getModelProperty;
else
View
7 REST/RESTBody.h
@@ -103,6 +103,13 @@
Returns nil if the string isn't parseable, or if it isn't a string at all. */
+ (NSDate*) dateWithJSONObject: (id)jsonObject;
+/** Converts an NSURL to a string. */
++ (NSString*) JSONObjectWithURL: (NSURL*)url;
+
+/** Parses a string into an NSURL.
+ Returns nil if the string isn't parseable, or if it isn't a string at all. */
++ (NSURL*) urlWithJSONObject: (id)jsonObject;
+
/** Encodes NSData to a Base64 string, which can be stored in JSON. */
+ (NSString*) base64WithData: (NSData*)data;
View
20 REST/RESTBody.m
@@ -355,6 +355,7 @@ + (NSString*) JSONObjectWithDate: (NSDate*)date {
}
}
+
+ (NSDate*) dateWithJSONObject: (id)jsonObject {
NSString* string = $castIf(NSString, jsonObject);
if (!string)
@@ -365,6 +366,25 @@ + (NSDate*) dateWithJSONObject: (id)jsonObject {
}
++ (NSString*) JSONObjectWithURL: (NSURL*)url {
+ if (!url)
+ return nil;
+ @synchronized(self) {
+ return [url absoluteString];
+ }
+}
+
+
++ (NSURL*) urlWithJSONObject: (id)jsonObject {
+ NSString* string = $castIf(NSString, jsonObject);
+ if (!string)
+ return nil;
+ @synchronized(self) {
+ return [NSURL URLWithString:string];
+ }
+}
+
+
+ (NSString*) base64WithData: (NSData*)data {
return [RESTBase64 encode: data];
}
View
3  REST/RESTResource.h
@@ -90,6 +90,9 @@
/** Starts an asynchronous HTTP DELETE operation. */
- (RESTOperation*) DELETE;
+/** Starts an asynchronous HTTP HEAD operation. */
+- (RESTOperation*) HEAD;
+
/** Sends an arbitrary HTTP request.
All the other HTTP request methods ultimately call this one.
@param method The HTTP method, e.g. @"GET". Remember to capitalize it.
View
5 REST/RESTResource.m
@@ -279,6 +279,11 @@ - (RESTOperation*) DELETE {
}
+- (RESTOperation*) HEAD {
+ return [self sendHTTP: @"HEAD" parameters: nil];
+}
+
+
#pragma mark -
#pragma mark CONTENT:
View
6 Test/Test_Couch.m
@@ -58,6 +58,10 @@ - (void) createDocuments: (unsigned)n {
#pragma mark - SERVER & DOCUMENTS:
+- (void) test00_Exists {
+ STAssertTrue(_server.exists, nil);
+ STAssertTrue(_db.exists, nil);
+}
- (void) test01_Server {
static const NSUInteger kUUIDCount = 5;
@@ -99,6 +103,8 @@ - (void) test02_CreateDocument {
RESTOperation* op = AssertWait([doc GET]);
STAssertEquals(op.httpStatus, 200, @"GET failed");
+
+ STAssertTrue(doc.exists, nil);
STAssertEqualObjects(doc.userProperties, properties, @"Couldn't get doc properties after GET");
}
View
29 Test/Test_Model.m
@@ -27,14 +27,16 @@ @implementation TestModel
@interface TestModelSubclass : TestModel
@property (readonly) NSString* type;
@property (readwrite, retain) NSString* status;
+@property (readwrite, copy) NSURL* homepage;
@end
@implementation TestModelSubclass
-@dynamic type, status;
-- (void)setDefaultValues {
- [self setValue:@"test-model" ofProperty:@"type"];
- [self setValue:@"inactive" ofProperty:@"status"];
- [self setValue:[NSArray array] ofProperty:@"otherNames"];
+@dynamic type, status, homepage;
+- (BOOL)setDefaultValues {
+ [self setDefault:@"test-model" ofProperty:@"type"];
+ [self setDefault:@"inactive" ofProperty:@"status"];
+ [self setDefault:[NSArray array] ofProperty:@"otherNames"];
+ return NO; // not ready for saving as-is
}
@end
@@ -49,9 +51,10 @@ @implementation Test_Model
- (void) test0_propertyNames {
NSSet* names = [NSSet setWithObjects: @"name", @"grade", @"permanentRecord", @"birthday", @"otherNames", @"buddy", nil];
STAssertEqualObjects([TestModel propertyNames], names, nil);
- NSSet* allNames = [NSSet setWithObjects: @"name", @"grade", @"permanentRecord", @"birthday", @"otherNames", @"buddy", @"type", @"status", nil];
+ NSSet* allNames = [NSSet setWithObjects: @"name", @"grade", @"permanentRecord", @"birthday", @"otherNames", @"buddy", @"type", @"status", @"homepage", nil];
STAssertEqualObjects([TestModelSubclass propertyNames], allNames, nil);
- STAssertEqualObjects([TestModelSubclass writablePropertyNames], [names setByAddingObject:@"status"], nil);
+ NSSet* additionalNames = [names setByAddingObjectsFromSet:[NSSet setWithObjects:@"status", @"homepage", nil]];
+ STAssertEqualObjects([TestModelSubclass writablePropertyNames], additionalNames, nil);
}
@@ -302,14 +305,21 @@ - (void) test7_setDefaultValues {
}
- (void) test9_setDefaultValues {
+ NSURL* homepage = [NSURL URLWithString:@"http://www.tweedledum.com"];
{
CouchDocument* doc = [_db documentWithID: @"0001"];
TestModelSubclass* student = [TestModelSubclass modelForDocument: doc];
STAssertTrue(student.isNew, nil);
+ STAssertFalse(student.needsSave, nil);
+
student.name = @"Tweedledum";
student.grade = 2;
+ student.homepage = homepage;
STAssertTrue(student.isNew, nil);
+ STAssertTrue(student.needsSave, nil);
+
+ STAssertEqualObjects(student.name, @"Tweedledum", nil);
STAssertEqualObjects(student.type, @"test-model", nil);
STAssertEqualObjects(student.status, @"inactive", nil);
@@ -324,6 +334,7 @@ - (void) test9_setDefaultValues {
TestModelSubclass* student = [TestModelSubclass modelForDocument: [_db documentWithID: @"0001"]];
STAssertFalse(student.isNew, nil);
STAssertEqualObjects(student.status, @"suspended", nil);
+ STAssertEqualObjects(student.homepage, homepage, nil);
}
- (void) test10_Properties {
@@ -481,8 +492,8 @@ - (void) test15_reload {
AssertWait([student save]);
}
[_db clearDocumentCache];
-
- TestModelSubclass* student = [TestModelSubclass modelForDocument: [_db documentWithID: @"0001"]];
+
+ TestModelSubclass* student = [TestModelSubclass modelForDocument: [_db documentWithID: @"0001"]];
STAssertEqualObjects(student.name, @"Alice", nil);
STAssertEqualObjects(student.status, @"active", nil);
STAssertEqualObjects(student.otherNames, otherNames, nil);
View
127 Test/Test_Security.m
@@ -0,0 +1,127 @@
+//
+// Test_Security.m
+// CouchCocoa
+//
+// Created by Fabien Franzen on 21-06-12.
+// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchInternal.h"
+#import "CouchTestCase.h"
+
+@interface Test_Security : CouchTestCase
+@end
+
+@implementation Test_Security
+
+- (void)tearDown {
+ CouchUser* user = [_server userWithName:@"Alice"];
+ if (!user.isNew) {
+ RESTOperation* op = [user deleteDocument];
+ if (![op wait]) NSLog(@"Failed to delete user: %@", user);
+ }
+ [super tearDown];
+}
+
+
+- (void) test0_userWithName {
+ NSArray* roles = [NSArray arrayWithObjects:@"viewer", nil];
+ {
+ CouchUser* user = [_server userWithName:@"Alice"];
+ STAssertEqualObjects(user.document.documentID, @"org.couchdb.user:alice", nil);
+ STAssertEqualObjects(user.name, @"alice", nil); // note how it auto-formats the name
+ STAssertEqualObjects(user.roles, [NSArray array], nil);
+
+ STAssertFalse(user.exists, nil);
+ STAssertTrue(user.needsSave, nil);
+
+ AssertWait([user save]);
+
+ STAssertTrue(user.exists, nil);
+ STAssertFalse(user.needsSave, nil);
+
+ // without a password, the following properties are empty
+ STAssertNil([user getValueOfProperty:@"derived_key"], nil);
+ STAssertNil([user getValueOfProperty:@"password_scheme"], nil);
+ STAssertNil([user getValueOfProperty:@"salt"], nil);
+ STAssertNil([user getValueOfProperty:@"iterations"], nil);
+
+ [user setPassword:@"secret"];
+ STAssertNotNil([user getValueOfProperty:@"password"], nil);
+
+ [user setRoles:roles];
+ STAssertEqualObjects(user.roles, roles, nil);
+
+ [user setValue:@"Alice" ofProperty:@"firstname"];
+ [user setValue:@"Wonderland" ofProperty:@"lastname"];
+
+ AssertWait([user save]);
+ }
+ [_db clearDocumentCache];
+
+ CouchUser* user = [_server userWithName:@"alice"];
+
+ STAssertNil([user getValueOfProperty:@"password"], nil);
+ STAssertEqualObjects(user.roles, roles, nil);
+
+ STAssertEqualObjects([user getValueOfProperty:@"firstname"], @"Alice", nil);
+ STAssertEqualObjects([user getValueOfProperty:@"lastname"], @"Wonderland", nil);
+
+ // with a password, the following properties will have some values
+ STAssertNotNil([user getValueOfProperty:@"derived_key"], nil);
+ STAssertNotNil([user getValueOfProperty:@"password_scheme"], nil);
+ STAssertNotNil([user getValueOfProperty:@"salt"], nil);
+ STAssertNotNil([user getValueOfProperty:@"iterations"], nil);
+}
+
+- (void) test1_security {
+ NSArray* names = [NSArray arrayWithObjects:@"alice", @"bob", nil];
+ NSArray* roles = [NSArray arrayWithObjects:@"boss", nil];
+ {
+ CouchSecurity* security = [_db security];
+
+ security.adminRoles = roles;
+ STAssertEqualObjects(security.adminRoles, roles, nil);
+
+ security.readerNames = names;
+ STAssertEqualObjects(security.readerNames, names, nil);
+
+ STAssertEqualObjects(security.adminNames, [NSArray array], nil);
+ STAssertEqualObjects(security.readerRoles, [NSArray array], nil);
+
+ AssertWait([security update]);
+ }
+
+ {
+ CouchSecurity* security = [_db security];
+ STAssertEqualObjects(security.adminRoles, roles, nil);
+ STAssertEqualObjects(security.readerNames, names, nil);
+
+ STAssertEqualObjects(security.adminNames, [NSArray array], nil);
+ STAssertEqualObjects(security.readerRoles, [NSArray array], nil);
+
+ [security addObject:@"bob" forProperty:kSecurityAdminNamesKey];
+ STAssertTrue([security.adminNames containsObject:@"bob"],nil);
+
+ [security addObject:@"viewer" forProperty:kSecurityReaderRolesKey];
+ STAssertTrue([security.readerRoles containsObject:@"viewer"],nil);
+
+ AssertWait([security update]);
+ }
+
+ {
+ CouchSecurity* security = [_db security];
+ STAssertEqualObjects(security.readerNames, names, nil);
+
+ STAssertTrue([security.adminNames containsObject:@"bob"],nil);
+ STAssertTrue([security.readerRoles containsObject:@"viewer"],nil);
+
+ [security removeObject:@"alice" forProperty:kSecurityReaderNamesKey];
+ STAssertEqualObjects(security.readerNames, [NSArray arrayWithObject:@"bob"], nil);
+
+ [security removeObject:@"viewer" forProperty:kSecurityReaderRolesKey];
+ STAssertEqualObjects(security.readerRoles, [NSArray array], nil);
+ }
+}
+
+@end
Please sign in to comment.
Something went wrong with that request. Please try again.