diff options
author | Max <post@wickenrode.com> | 2016-02-29 01:40:49 +0100 |
---|---|---|
committer | Max <post@wickenrode.com> | 2016-02-29 01:40:49 +0100 |
commit | bff1773bf4eeb79fc74e42cab68a98518f5a3198 (patch) | |
tree | 082210d08705e341bbff757cb4c7989801f080e8 | |
parent | 6c1c212be52dd6c9bf5a8cd01bad317c84e73659 (diff) | |
download | sequelpro-bff1773bf4eeb79fc74e42cab68a98518f5a3198.tar.gz sequelpro-bff1773bf4eeb79fc74e42cab68a98518f5a3198.tar.bz2 sequelpro-bff1773bf4eeb79fc74e42cab68a98518f5a3198.zip |
* Add support for changing user passwords in MySQL 5.7.6+ (#2418)
* Upgrade xcdatamodel to Xcode 4.3+ so it becomes a diffable textfile
-rw-r--r-- | Models/SPUserManager.xcdatamodel/contents | 81 | ||||
-rw-r--r-- | Models/SPUserManager.xcdatamodel/elements | bin | 226675 -> 0 bytes | |||
-rw-r--r-- | Models/SPUserManager.xcdatamodel/layout | bin | 9467 -> 0 bytes | |||
-rw-r--r-- | Source/SPUserManager.h | 3 | ||||
-rw-r--r-- | Source/SPUserManager.m | 87 |
5 files changed, 150 insertions, 21 deletions
diff --git a/Models/SPUserManager.xcdatamodel/contents b/Models/SPUserManager.xcdatamodel/contents new file mode 100644 index 00000000..20b8ce73 --- /dev/null +++ b/Models/SPUserManager.xcdatamodel/contents @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6751" systemVersion="13F1507" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic"> + <entity name="Privileges" representedClassName="SPPrivilegesMO" syncable="YES"> + <attribute name="alter_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="alter_routine_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_routine_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_temporary_tables_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_view_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="db" attributeType="String" maxValueString="64" indexed="YES" syncable="YES"/> + <attribute name="delete_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="drop_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="event_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="execute_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="grant_option_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="index_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="insert_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="lock_tables_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="references_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="select_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="show_view_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="trigger_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="update_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="userManager" optional="YES" transient="YES" syncable="YES"/> + <relationship name="user" optional="YES" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="SPUser" inverseName="schema_privileges" inverseEntity="SPUser" indexed="YES" syncable="YES"/> + </entity> + <entity name="SPUser" representedClassName="SPUserMO" syncable="YES"> + <attribute name="alter_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="alter_routine_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="authentication_string" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="create_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_routine_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_tablespace_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_temporary_tables_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_user_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_view_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="delete_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="drop_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="event_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="execute_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="file_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="grant_option_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="host" optional="YES" attributeType="String" maxValueString="60" defaultValueString="%" syncable="YES"/> + <attribute name="index_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="insert_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="lock_tables_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="max_connections" optional="YES" attributeType="Integer 32" minValueString="0" maxValueString="99999999999" defaultValueString="0" syncable="YES"/> + <attribute name="max_questions" optional="YES" attributeType="Integer 32" minValueString="0" maxValueString="99999999999" defaultValueString="0" syncable="YES"/> + <attribute name="max_updates" optional="YES" attributeType="Integer 32" minValueString="0" maxValueString="99999999999" defaultValueString="0" syncable="YES"/> + <attribute name="originalhost" optional="YES" attributeType="String" maxValueString="60" defaultValueString="%" syncable="YES"/> + <attribute name="originalpassword" optional="YES" attributeType="String"> + <userInfo/> + </attribute> + <attribute name="originaluser" optional="YES" attributeType="String"> + <userInfo/> + </attribute> + <attribute name="password" optional="YES" attributeType="String" maxValueString="41" syncable="YES"/> + <attribute name="plugin" optional="YES" attributeType="String" maxValueString="64" syncable="YES"/> + <attribute name="process_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="references_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="reload_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="replication_client_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="replication_slave_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="select_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="show_databases_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="show_view_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="shutdown_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="super_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="trigger_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="update_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="user" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="userManager" optional="YES" transient="YES" syncable="YES"/> + <relationship name="children" optional="YES" toMany="YES" minCount="1" deletionRule="Cascade" destinationEntity="SPUser" inverseName="parent" inverseEntity="SPUser" indexed="YES" syncable="YES"/> + <relationship name="parent" optional="YES" minCount="1" maxCount="1" deletionRule="Cascade" destinationEntity="SPUser" inverseName="children" inverseEntity="SPUser" indexed="YES" syncable="YES"/> + <relationship name="schema_privileges" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Privileges" inverseName="user" inverseEntity="Privileges" indexed="YES" syncable="YES"/> + </entity> + <elements> + <element name="Privileges" positionX="412" positionY="90" width="128" height="373"/> + <element name="SPUser" positionX="135" positionY="45" width="207" height="705"/> + </elements> +</model>
\ No newline at end of file diff --git a/Models/SPUserManager.xcdatamodel/elements b/Models/SPUserManager.xcdatamodel/elements Binary files differdeleted file mode 100644 index 9723af21..00000000 --- a/Models/SPUserManager.xcdatamodel/elements +++ /dev/null diff --git a/Models/SPUserManager.xcdatamodel/layout b/Models/SPUserManager.xcdatamodel/layout Binary files differdeleted file mode 100644 index 7fe6d9bf..00000000 --- a/Models/SPUserManager.xcdatamodel/layout +++ /dev/null diff --git a/Source/SPUserManager.h b/Source/SPUserManager.h index 3bee8636..6338145f 100644 --- a/Source/SPUserManager.h +++ b/Source/SPUserManager.h @@ -80,6 +80,9 @@ BOOL isSaving; BOOL isInitializing; NSMutableString *errorsString; + + // MySQL 5.7.6 removes the "Password" columns and only uses the "plugin"+"authentication_string" columns + BOOL requiresPost576PasswordHandling; } @property (nonatomic, retain) SPMySQLConnection *connection; diff --git a/Source/SPUserManager.m b/Source/SPUserManager.m index 6f42ac75..a5a83cbe 100644 --- a/Source/SPUserManager.m +++ b/Source/SPUserManager.m @@ -159,6 +159,9 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; // Select users from the mysql.user table SPMySQLResult *result = [connection queryString:@"SELECT * FROM mysql.user ORDER BY user"]; [result setReturnDataAsStrings:YES]; + //TODO: improve user feedback + NSAssert(([[result fieldNames] firstObjectCommonWithArray:@[@"Password",@"authentication_string"]] != nil), @"Resultset from mysql.user contains neither 'Password' nor 'authentication_string' column!?"); + requiresPost576PasswordHandling = ![[result fieldNames] containsObject:@"Password"]; [usersResultArray addObjectsFromArray:[result getAllRows]]; [self _initializeTree:usersResultArray]; @@ -244,9 +247,9 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; // for each user currently in the database. for (NSUInteger i = 0; i < [items count]; i++) { - NSString *username = [[items objectAtIndex:i] objectForKey:@"User"]; - NSArray *parentResults = [[self _fetchUserWithUserName:username] retain]; NSDictionary *item = [items objectAtIndex:i]; + NSString *username = [item objectForKey:@"User"]; + NSArray *parentResults = [[self _fetchUserWithUserName:username] retain]; SPUserMO *parent; SPUserMO *child; @@ -266,8 +269,16 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; // original values for comparison purposes [parent setPrimitiveValue:username forKey:@"user"]; [parent setPrimitiveValue:username forKey:@"originaluser"]; - [parent setPrimitiveValue:[item objectForKey:@"Password"] forKey:@"password"]; - [parent setPrimitiveValue:[item objectForKey:@"Password"] forKey:@"originalpassword"]; + if(requiresPost576PasswordHandling) { + [parent setPrimitiveValue:[item objectForKey:@"plugin"] forKey:@"plugin"]; + NSString *pwHash = [item objectForKey:@"authentication_string"]; + [parent setPrimitiveValue:pwHash forKey:@"authentication_string"]; + if([pwHash length]) [parent setPrimitiveValue:@"sequelpro_dummy_password" forKey:@"password"]; // for the UI dialog + } + else { + [parent setPrimitiveValue:[item objectForKey:@"Password"] forKey:@"password"]; + [parent setPrimitiveValue:[item objectForKey:@"Password"] forKey:@"originalpassword"]; + } } // Setup the NSManagedObject with values from the dictionary @@ -360,7 +371,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; NSNumber *value = [NSNumber numberWithInteger:[[item objectForKey:key] integerValue]]; [child setValue:value forKey:key]; } - else if (![key isEqualToString:@"User"] && ![key isEqualToString:@"Password"]) + else if (![key isInArray:@[@"User",@"Password",@"plugin",@"authentication_string"]]) { NSString *value = [item objectForKey:key]; [child setValue:value forKey:key]; @@ -807,7 +818,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; { if (![user parent]) { [user setPrimitiveValue:[user valueForKey:@"user"] forKey:@"originaluser"]; - [user setPrimitiveValue:[user valueForKey:@"password"] forKey:@"originalpassword"]; + if(!requiresPost576PasswordHandling) [user setPrimitiveValue:[user valueForKey:@"password"] forKey:@"originalpassword"]; } } } @@ -961,20 +972,43 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; } // If the password has been changed, use the same password on all hosts - if (![[user valueForKey:@"password"] isEqualToString:[user valueForKey:@"originalpassword"]]) { - - for (SPUserMO *child in hosts) - { - NSString *changePasswordStatement = [NSString stringWithFormat: - @"SET PASSWORD FOR %@@%@ = PASSWORD(%@)", - [[user valueForKey:@"user"] tickQuotedString], - [[child host] tickQuotedString], - ([user valueForKey:@"password"]) ? [[user valueForKey:@"password"] tickQuotedString] : @"''"]; - - [connection queryString:changePasswordStatement]; + if(requiresPost576PasswordHandling) { + // the UI password field is bound to the password field, so this is still where the new plaintext value comes from + NSString *newPass = [[user changedValues] objectForKey:@"password"]; + if(newPass) { + // 5.7.6+ can update all users at once + NSMutableString *alterStmt = [NSMutableString stringWithString:@"ALTER USER "]; + BOOL first = YES; + for (SPUserMO *child in hosts) + { + if(!first) [alterStmt appendString:@", "]; + [alterStmt appendFormat:@"%@@%@ IDENTIFIED WITH %@ BY %@", //note: "BY" -> plaintext, "AS" -> hash + [[user valueForKey:@"user"] tickQuotedString], + [[child host] tickQuotedString], + [[user valueForKey:@"plugin"] tickQuotedString], + (![newPass isNSNull] && [newPass length]) ? [newPass tickQuotedString] : @"''"]; + first = NO; + } + [connection queryString:alterStmt]; if(![self _checkAndDisplayMySqlError]) return NO; } } + else { + if (![[user valueForKey:@"password"] isEqualToString:[user valueForKey:@"originalpassword"]]) { + + for (SPUserMO *child in hosts) + { + NSString *changePasswordStatement = [NSString stringWithFormat: + @"SET PASSWORD FOR %@@%@ = PASSWORD(%@)", + [[user valueForKey:@"user"] tickQuotedString], + [[child host] tickQuotedString], + ([user valueForKey:@"password"]) ? [[user valueForKey:@"password"] tickQuotedString] : @"''"]; + + [connection queryString:changePasswordStatement]; + if(![self _checkAndDisplayMySqlError]) return NO; + } + } + } } else { // If the hostname has changed, remane the detail before editing details @@ -1038,14 +1072,25 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; // same affect as CREATE USER. That is, a new user with no privleges. NSString *host = [[user valueForKey:@"host"] tickQuotedString]; - if ([user parent] && [[user parent] valueForKey:@"user"] && [[user parent] valueForKey:@"password"]) { + if ([user parent] && [[user parent] valueForKey:@"user"] && ([[user parent] valueForKey:@"password"] || [[user parent] valueForKey:@"authentication_string"])) { NSString *username = [[[user parent] valueForKey:@"user"] tickQuotedString]; - NSString *password = [[[user parent] valueForKey:@"password"] tickQuotedString]; + + NSString *idString; + if(requiresPost576PasswordHandling) { + //copy the hash from the parent. if the parent password changes at the same time, updateUser: will take care of it afterwards + NSString *plugin = [[[user parent] valueForKey:@"plugin"] tickQuotedString]; + NSString *hash = [[[user parent] valueForKey:@"authentication_string"] tickQuotedString]; + idString = [NSString stringWithFormat:@"IDENTIFIED WITH %@ AS %@",plugin,hash]; + } + else { + NSString *password = [[[user parent] valueForKey:@"password"] tickQuotedString]; + idString = [NSString stringWithFormat:@"IDENTIFIED BY %@%@",[[user parent] valueForKey:@"originaluser"]?@"PASSWORD ":@"", password]; + } createStatement = ([serverSupport supportsCreateUser]) ? - [NSString stringWithFormat:@"CREATE USER %@@%@ IDENTIFIED BY %@%@", username, host, [[user parent] valueForKey:@"originaluser"]?@"PASSWORD ":@"", password] : - [NSString stringWithFormat:@"GRANT SELECT ON mysql.* TO %@@%@ IDENTIFIED BY %@%@", username, host, [[user parent] valueForKey:@"originaluser"]?@"PASSWORD ":@"", password]; + [NSString stringWithFormat:@"CREATE USER %@@%@ %@", username, host, idString] : + [NSString stringWithFormat:@"GRANT SELECT ON mysql.* TO %@@%@ %@", username, host, idString]; } else if ([user parent] && [[user parent] valueForKey:@"user"]) { |