Skip to content

Commit

Permalink
reset password for Mac App 😋
Browse files Browse the repository at this point in the history
  • Loading branch information
camsaul committed Nov 25, 2015
1 parent 59f1128 commit 1f5f68a
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 114 deletions.
68 changes: 19 additions & 49 deletions OSX/Metabase/Backend/JavaTask.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,46 +30,17 @@


@interface JavaTask ()
@property (strong, nonatomic, readwrite) NSPipe *pipe;
@property (strong, nonatomic, readwrite) NSFileHandle *readHandle;
@property (nonatomic, strong) NSPipe *pipe;
@property (nonatomic, strong) NSFileHandle *readHandle;
@end

@implementation JavaTask

#pragma mark - Lifecycle

- (instancetype)init {
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fileHandleCompletedRead:) name:NSFileHandleReadCompletionNotification object:nil];
}
return self;
}

- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self terminate];
}


#pragma mark - Notifications

- (void)fileHandleCompletedRead:(NSNotification *)notification {
if (!self.readHandle || notification.object != self.readHandle) return;

__weak JavaTask *weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
if (!weakSelf) return;

NSData *data = notification.userInfo[NSFileHandleNotificationDataItem];
if (data.length) [weakSelf readHandleDidReadData:data];

dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.readHandle readInBackgroundAndNotify];
});
});
}



#pragma mark - Local Methods

Expand Down Expand Up @@ -99,39 +70,38 @@ - (void)setTask:(NSTask *)task {
_task = task;

if (task) {
self.pipe = [NSPipe pipe];
self.task.standardOutput = self.pipe;
self.task.standardError = self.pipe;
self.pipe = [NSPipe pipe];
task.standardOutput = self.pipe;
task.standardError = self.pipe;
}
}

- (void)setPipe:(NSPipe *)pipe {
self.readHandle = nil;

_pipe = pipe;

if (pipe) {
self.readHandle = pipe.fileHandleForReading;
__weak JavaTask *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
if (weakSelf) [weakSelf.readHandle readInBackgroundAndNotify];
});
}
}

- (void)setReadHandle:(NSFileHandle *)readHandle {
// handle any remaining data in the read handle before closing, if applicable
if (_readHandle) {
NSData *data;
@try {
data = [_readHandle availableData];
}
@catch (NSException *exception) {}

if (data.length) [self readHandleDidReadData:data];
}

[_readHandle closeFile];

_readHandle = readHandle;

if (readHandle) {
__weak JavaTask *weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
while (readHandle == weakSelf.readHandle) {
NSData *data = readHandle.availableData;
if (!data.length) return;

[weakSelf readHandleDidReadData:data];
}
});
}
}

@end
76 changes: 41 additions & 35 deletions OSX/Metabase/Backend/ResetPasswordTask.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@

#import "ResetPasswordTask.h"

NSString *ResetPasswordJarPath() {
return [[NSBundle mainBundle] pathForResource:@"reset-password" ofType:@"jar"];
}

@interface ResetPasswordTask ()
@property (nonatomic, readonly) NSString *resetPasswordJarPath;
@property (copy) NSString *output;
@end

Expand All @@ -20,56 +23,59 @@ @implementation ResetPasswordTask

- (void)resetPasswordForEmailAddress:(NSString *)emailAddress success:(void (^)(NSString *))successBlock error:(void (^)(NSString *))errorBlock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
self.task = [[NSTask alloc] init];
self.task.launchPath = JREPath();
self.task.arguments = @[@"-classpath", [NSString stringWithFormat:@"%@:%@", UberjarPath(), self.resetPasswordJarPath],
@"metabase.reset_password.core",
DBPath(), emailAddress];
self.task = [[NSTask alloc] init];

NSString *dbPath = [DBPath() stringByAppendingString:@";IFEXISTS=TRUE"];
self.task.environment = @{@"MB_DB_FILE": dbPath, @"HOME": @"/Users/camsaul"};

// time travelers from the future: this is hardcoded since I'm the only one who works on this. I give you permission to fix it - Cam
#define DEBUG_RUN_LEIN_TASK 0

#if DEBUG_RUN_LEIN_TASK
self.task.environment = @{@"MB_DB_FILE": dbPath};
self.task.currentDirectoryPath = @"/Users/camsaul/metabase";
self.task.launchPath = @"/Users/camsaul/scripts/lein";
self.task.arguments = @[@"with-profile", @"reset-password", @"run", emailAddress];
NSLog(@"Launching ResetPasswordTask\nMB_DB_FILE='%@' lein with-profile reset-password run %@", dbPath, emailAddress);
#else
self.task.environment = @{@"MB_DB_FILE": dbPath};
self.task.launchPath = JREPath();
self.task.arguments = @[@"-classpath", [NSString stringWithFormat:@"%@:%@", UberjarPath(), ResetPasswordJarPath()],
@"metabase.reset_password.core", emailAddress];
NSLog(@"Launching ResetPasswordTask\nMB_DB_FILE='%@' %@ -classpath %@:%@ metabase.reset_password.core %@", dbPath, JREPath(), UberjarPath(), ResetPasswordJarPath(), emailAddress);
#endif

__weak ResetPasswordTask *weakSelf = self;
self.task.terminationHandler = ^(NSTask *task) {
NSLog(@"ResetPasswordTask terminated with status: %d", task.terminationStatus);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5f * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[weakSelf terminate];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5f * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
if (!task.terminationStatus && weakSelf.output.length) {
successBlock(weakSelf.output);
} else {
errorBlock(weakSelf.output.length ? weakSelf.output : @"An unknown error has occured.");
}
});
});
[weakSelf terminate];

dispatch_async(dispatch_get_main_queue(), ^{
if (!task.terminationStatus && weakSelf.output.length >= 38) { // should be of format <user-id>_<36-char-uuid>, e.g. "1_b20466b9-1f5b-488d-8ab6-5039107482f8"
successBlock(weakSelf.output);
} else {
errorBlock(weakSelf.output.length ? weakSelf.output : @"An unknown error has occured.");
}
});
};

NSLog(@"Launching ResetPasswordTask\n%@ -classpath %@:%@ metabase.reset_password.core %@ %@", JREPath(), UberjarPath(), self.resetPasswordJarPath, DBPath(), emailAddress);
// delay lauch just a second to make sure pipe is all set up, etc.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5f * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.task launch];
});
[self.task launch];
});
}

- (void)readHandleDidRead:(NSString *)message {
NSLog(@"[PasswordResetTask] %@", message);

/// output comes back like "STATUS [[[message]]]"
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(?:(?:OK)||(?:FAIL))\\s+\\[\\[\\[(.+)\\]\\]\\]\\s*$" options:NSRegularExpressionAnchorsMatchLines|NSRegularExpressionAllowCommentsAndWhitespace error:NULL];
NSString *result = [regex stringByReplacingMatchesInString:message options:0 range:NSMakeRange(0, message.length) withTemplate:@"$1"];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(?:(?:OK)||(?:FAIL))\\s+\\[\\[\\[(.+)\\]\\]\\]\\s*$" options:NSRegularExpressionAnchorsMatchLines|NSRegularExpressionAllowCommentsAndWhitespace error:NULL];

if (![regex numberOfMatchesInString:message options:0 range:NSMakeRange(0, message.length)]) return;

NSString *result = [regex stringByReplacingMatchesInString:message options:0 range:NSMakeRange(0, message.length) withTemplate:@"$1"];
if (result) {
self.output = result;
NSLog(@"[PasswordResetTask] %@", self.output);
} else {
NSLog(@"[PasswordResetTask - Bad Output] %@", message);
NSLog(@"[PasswordResetTask] task output is '%@'", self.output);
}
}


#pragma mark - Getters / Setters

- (NSString *)resetPasswordJarPath {
return [[NSBundle mainBundle] pathForResource:@"reset-password" ofType:@"jar"];
}

@end
14 changes: 7 additions & 7 deletions OSX/Metabase/Metabase-Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,20 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.13.0.0</string>
<string>0.12.1.991</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.13.0.0</string>
<string>0.12.1.991</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015 Metabase. All rights reserved.</string>
<key>NSMainNibFile</key>
Expand All @@ -38,10 +43,5 @@
<string>https://s3.amazonaws.com/downloads.metabase.com/appcast.xml</string>
<key>SUPublicDSAKeyFile</key>
<string>dsa_pub.pem</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>
18 changes: 14 additions & 4 deletions OSX/Metabase/UI/MainViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ @interface MainViewController () <ResetPasswordWindowControllerDelegate>
@property (weak) IBOutlet NSButtonCell *refreshButtonCell;
@property (weak) IBOutlet NSButtonCell *linkButtonCell;

@property (nonatomic, strong) ResetPasswordWindowController *resetPasswordWindowController;
@property (weak) LoadingView *loadingView;

@property (nonatomic) BOOL loading;
Expand Down Expand Up @@ -181,6 +182,15 @@ - (void)setLoading:(BOOL)loading {
self.loadingView.animate = loading;
}

- (void)setResetPasswordWindowController:(ResetPasswordWindowController *)resetPasswordWindowController {
[_resetPasswordWindowController.window close];

_resetPasswordWindowController = resetPasswordWindowController;

resetPasswordWindowController.delegate = self;
[resetPasswordWindowController.window makeKeyWindow];
}


#pragma mark - Actions

Expand All @@ -204,17 +214,17 @@ - (IBAction)copyURLToClipboard:(id)sender {
}

- (IBAction)resetPassword:(id)sender {
ResetPasswordWindowController *resetPasswordWindowController = [[ResetPasswordWindowController alloc] init];
resetPasswordWindowController.delegate = self;
[[NSApplication sharedApplication] runModalForWindow:resetPasswordWindowController.window];
self.resetPasswordWindowController = [[ResetPasswordWindowController alloc] init];
}


#pragma mark - ResetPasswordWindowControllerDelegate

- (void)resetPasswordWindowController:(ResetPasswordWindowController *)resetPasswordWindowController didFinishWithResetToken:(NSString *)resetToken {
self.resetPasswordWindowController = nil;

NSString *passwordResetURLString = [NSString stringWithFormat:@"%@/auth/reset_password/%@", BaseURL(), resetToken];
NSLog(@"Navigating to password reset URL: %@...", passwordResetURLString);
NSLog(@"Navigating to password reset URL '%@'...", passwordResetURLString);

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:passwordResetURLString]];
[self.webView.mainFrame loadRequest:request];
Expand Down
1 change: 1 addition & 0 deletions OSX/Metabase/UI/ResetPasswordWindowController.m
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ - (IBAction)emailAddressTextFieldDidReturn:(id)sender {
#pragma mark - NSTextFieldDelegate

- (void)controlTextDidChange:(NSNotification *)obj {
self.resetPasswordButton.title = @"Reset Password";
self.resetPasswordButton.enabled = self.emailAddressTextField.stringValue.length > 5;
}

Expand Down
2 changes: 1 addition & 1 deletion bin/osx-release
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ sub announce_on_slack {
Readonly my $slack_url => config('slackWebhookURL') or return;
Readonly my $version => version();
Readonly my $awsURL => 'https://s3.amazonaws.com/' . config('awsBucket') . '/' . upload_subdir() . '/Metabase.dmg';
my $text = "Metabase OS X $version 'Turbulent Toucan' Is Now Available!\n\n" .
my $text = "Metabase OS X $version 'Tumultuous Toucan' Is Now Available!\n\n" .
"Get it here: $awsURL\n\n";

open(my $file, get_file_or_die($release_notes)) or die $!;
Expand Down
9 changes: 6 additions & 3 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
:target-path "target/%s"
:javac-options ["-target" "1.6" "-source" "1.6"]
:uberjar-name "metabase.jar"
:omit-source true
:ring {:handler metabase.core/app
:init metabase.core/init
:destroy metabase.core/destroy}
Expand Down Expand Up @@ -101,13 +102,15 @@
:source-paths ["sample_dataset"]
:global-vars {*warn-on-reflection* false}
:main ^:skip-aot metabase.sample-dataset.generate}
;; Run reset password from source: lein with-profile reset-password run /path/to/metabase.db [email protected]
;; Run reset password from source: MB_DB_PATH=/path/to/metabase.db lein with-profile reset-password run [email protected]
;; Create the reset password JAR: lein with-profile reset-password jar
;; -> ./reset-password-artifacts/reset-password/reset-password.jar
;; Run the reset password JAR: java -classpath /path/to/metabase-uberjar.jar:/path/to/reset-password.jar \
;; metabase.reset_password.core /path/to/metabase.db [email protected]
;; Run the reset password JAR: MB_DB_PATH=/path/to/metabase.db java -classpath /path/to/metabase-uberjar.jar:/path/to/reset-password.jar \
;; metabase.reset_password.core [email protected]
:reset-password {:source-paths ["reset_password"]
:global-vars {*warn-on-reflection* false}
:main metabase.reset-password.core
:jar-name "reset-password.jar"
;; Exclude everything except for reset-password specific code in the created jar
:jar-exclusions [#"^(?!metabase/reset_password).*$"]
:target-path "reset-password-artifacts/%s"}}) ; different than ./target because otherwise lein uberjar will delete our artifacts and vice versa
27 changes: 13 additions & 14 deletions reset_password/metabase/reset_password/core.clj
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
(ns metabase.reset-password.core
(:gen-class)
(:require [clojure.java.jdbc :as jdbc]))
(:require [metabase.db :as db]
[metabase.models.user :as user]))

(defn- db-filepath->connection-details [filepath]
{:classname "org.h2.Driver"
:subprotocol "h2"
:subname (str "file:" filepath ";MV_STORE=FALSE;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1")})

(defn- set-reset-token! [dbpath email-address reset-token]
(let [rows-affected (jdbc/execute! (db-filepath->connection-details dbpath) ["UPDATE CORE_USER SET RESET_TOKEN = ?, RESET_TRIGGERED = ? WHERE EMAIL = ?;" reset-token (System/currentTimeMillis) email-address])]
(when (not= rows-affected [1])
(throw (Exception. (format "No user found with email address '%s'. Please check the spelling and try again." email-address))))))
(defn- set-reset-token!
"Set and return a new `reset_token` for the user with EMAIL-ADDRESS."
[email-address]
(let [user-id (or (db/sel :one :id 'User, :email email-address)
(throw (Exception. (format "No user found with email address '%s'. Please check the spelling and try again." email-address))))]
(user/set-user-password-reset-token user-id)))

(defn -main
[dbpath email-address]
[email-address]
(db/setup-db)
(println (format "Resetting password for %s..." email-address))
(try
(let [reset-token (str (java.util.UUID/randomUUID))]
(set-reset-token! dbpath email-address reset-token)
(println (format "OK [[[%s]]]" reset-token)))
(println (format "OK [[[%s]]]" (set-reset-token! email-address)))
(System/exit 0)
(catch Throwable e
(println (format "FAIL [[[%s]]]" (.getMessage e)))
(System/exit -1))))
1 change: 1 addition & 0 deletions src/metabase/db.clj
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
{:pre [(keyword? engine) (map? details)]}
(log/info "Verifying Database Connection ...")
(assert (binding [*allow-potentailly-unsafe-connections* true]
(require 'metabase.driver)
(@(resolve 'metabase.driver/can-connect-with-details?) engine details))
"Unable to connect to Metabase DB.")
(log/info "Verify Database Connection ... CHECK"))
Expand Down
2 changes: 1 addition & 1 deletion src/metabase/models/user.clj
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"Updates a given `User` and generates a password reset token for them to use. Returns the url for password reset."
[user-id]
{:pre [(integer? user-id)]}
(let [reset-token (str user-id "_" (java.util.UUID/randomUUID))]
(let [reset-token (str user-id \_ (java.util.UUID/randomUUID))]
(upd User user-id, :reset_token reset-token, :reset_triggered (System/currentTimeMillis))
;; return the token
reset-token))
Expand Down

0 comments on commit 1f5f68a

Please sign in to comment.