Skip to content

Commit

Permalink
* Add support for changing user passwords in MySQL 5.7.6+ (#2418)
Browse files Browse the repository at this point in the history
* Upgrade xcdatamodel to Xcode 4.3+ so it becomes a diffable textfile
  • Loading branch information
dmoagx committed Feb 29, 2016
1 parent 6c1c212 commit bff1773
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 21 deletions.
81 changes: 81 additions & 0 deletions Models/SPUserManager.xcdatamodel/contents
Original file line number Diff line number Diff line change
@@ -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>
Binary file removed Models/SPUserManager.xcdatamodel/elements
Binary file not shown.
Binary file removed Models/SPUserManager.xcdatamodel/layout
Binary file not shown.
3 changes: 3 additions & 0 deletions Source/SPUserManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
87 changes: 66 additions & 21 deletions Source/SPUserManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ - (void)_initializeUsers
// 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];
Expand Down Expand Up @@ -244,9 +247,9 @@ - (void)_initializeTree:(NSArray *)items
// 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;

Expand All @@ -266,8 +269,16 @@ - (void)_initializeTree:(NSArray *)items
// 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
Expand Down Expand Up @@ -360,7 +371,7 @@ - (void)_initializeChild:(NSManagedObject *)child withItem:(NSDictionary *)item
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];
Expand Down Expand Up @@ -807,7 +818,7 @@ - (IBAction)refresh:(id)sender
{
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"];
}
}
}
Expand Down Expand Up @@ -961,20 +972,43 @@ - (BOOL)updateUser:(SPUserMO *)user
}

// 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
Expand Down Expand Up @@ -1038,14 +1072,25 @@ - (BOOL)insertUser:(SPUserMO *)user
// 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"]) {

Expand Down

0 comments on commit bff1773

Please sign in to comment.