aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMax <post@wickenrode.com>2016-02-29 01:40:49 +0100
committerMax <post@wickenrode.com>2016-02-29 01:40:49 +0100
commitbff1773bf4eeb79fc74e42cab68a98518f5a3198 (patch)
tree082210d08705e341bbff757cb4c7989801f080e8
parent6c1c212be52dd6c9bf5a8cd01bad317c84e73659 (diff)
downloadsequelpro-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/contents81
-rw-r--r--Models/SPUserManager.xcdatamodel/elementsbin226675 -> 0 bytes
-rw-r--r--Models/SPUserManager.xcdatamodel/layoutbin9467 -> 0 bytes
-rw-r--r--Source/SPUserManager.h3
-rw-r--r--Source/SPUserManager.m87
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
deleted file mode 100644
index 9723af21..00000000
--- a/Models/SPUserManager.xcdatamodel/elements
+++ /dev/null
Binary files differ
diff --git a/Models/SPUserManager.xcdatamodel/layout b/Models/SPUserManager.xcdatamodel/layout
deleted file mode 100644
index 7fe6d9bf..00000000
--- a/Models/SPUserManager.xcdatamodel/layout
+++ /dev/null
Binary files differ
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"]) {