Skip to content

Commit

Permalink
Implement two-factor authentication support
Browse files Browse the repository at this point in the history
  • Loading branch information
atermenji committed Nov 5, 2013
1 parent 1487338 commit d6ba3f9
Show file tree
Hide file tree
Showing 10 changed files with 626 additions and 27 deletions.
10 changes: 10 additions & 0 deletions app/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,16 @@
someone who explicitly knows the class name
-->
</activity>
<activity
android:name=".accounts.TwoFactorAuthActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:excludeFromRecents="true" >

<!--
No intent-filter here! This activity is only ever launched by
someone who explicitly knows the class name
-->
</activity>
<activity
android:name=".ui.user.UriLauncherActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
Expand Down
62 changes: 62 additions & 0 deletions app/res/layout/login_two_factor_auth.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2013 GitHub Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<TextView
android:id="@+id/tv_signup"
style="@style/SubtitleText"
android:layout_width="match_parent"
android:background="@drawable/sign_up_background"
android:gravity="center"
android:padding="5dp"
android:textColor="@color/sign_up_text"
android:textColorLink="@color/sign_up_text_link" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="10dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:paddingTop="5dp" >

<TextView
style="@style/HeaderTitleText"
android:paddingTop="10dp"
android:text="@string/enter_otp_code_title" />

<EditText
android:id="@+id/et_otp_code"
style="@style/LoginEditText"
android:layout_marginTop="5dp"
android:gravity="center"
android:imeOptions="actionDone"
android:inputType="number"
android:maxLength="6" />

<TextView
style="@style/SubtitleText"
android:paddingTop="10dp"
android:text="@string/enter_otp_code_message"
android:textColor="@color/text" />

</LinearLayout>

</LinearLayout>
3 changes: 3 additions & 0 deletions app/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@
<string name="select_milestone">Select Milestone</string>
<string name="select_labels">Select Labels</string>
<string name="select_ref">Select Branch or Tag</string>
<string name="enter_otp_code_title">Authentication Code</string>
<string name="enter_otp_code_message">Two-factor authentication is enabled for your account. Enter your authentication code to verify your identity.</string>
<string name="no_milestone">No milestone</string>
<string name="unassigned">No one is assigned</string>
<string name="assigned">is assigned</string>
Expand Down Expand Up @@ -179,6 +181,7 @@
<string name="section_issue_labels">Labels:</string>
<string name="log_in">Log in</string>
<string name="signup_link">New to GitHub? &lt;a href=\"https://github.com/plans\">Click here&lt;/a> to sign up</string>
<string name="signup_link_two_factor_auth">Not sure what to do? &lt;a href=\"https://help.github.com/articles/about-two-factor-authentication\">Get some help.&lt;/a></string>
<string name="connection_failed">Unable to connect to GitHub</string>
<string name="invalid_login_or_password">Please enter a valid login &amp; password</string>
<string name="invalid_password">Please enter a valid password.</string>
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/com/github/mobile/RequestCodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,9 @@ public interface RequestCodes {
* Request to view a repository
*/
int REPOSITORY_VIEW = 12;

/**
* Request to enter two-factor authentication OTP code
*/
int OTP_CODE_ENTER = 13;
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class AccountAuthenticator extends AbstractAccountAuthenticator {

private static final String TAG = "GitHubAccountAuthenticator";

private static final List<String> SCOPES = Arrays.asList("repo", "user", "gist");

private Context context;

public AccountAuthenticator(final Context context) {
Expand Down Expand Up @@ -90,7 +92,7 @@ public Bundle editProperties(final AccountAuthenticatorResponse response,
return null;
}

private boolean isValidAuthorization(final Authorization auth,
private static boolean isValidAuthorization(final Authorization auth,
final List<String> requiredScopes) {
if (auth == null)
return false;
Expand All @@ -116,14 +118,12 @@ private Intent createLoginIntent(final AccountAuthenticatorResponse response) {
* Get existing authorization for this app
*
* @param service
* @param scopes
* @return token or null if none found
* @throws IOException
*/
private String getAuthorization(final OAuthService service,
final List<String> scopes) throws IOException {
public static String getAuthorization(final OAuthService service) throws IOException {
for (Authorization auth : service.getAuthorizations())
if (isValidAuthorization(auth, scopes))
if (isValidAuthorization(auth, SCOPES))
return auth.getToken();
return null;
}
Expand All @@ -132,16 +132,14 @@ private String getAuthorization(final OAuthService service,
* Create authorization for this app
*
* @param service
* @param scopes
* @return created token
* @throws IOException
*/
private String createAuthorization(final OAuthService service,
final List<String> scopes) throws IOException {
public static String createAuthorization(final OAuthService service) throws IOException {
Authorization auth = new Authorization();
auth.setNote(APP_NOTE);
auth.setNoteUrl(APP_NOTE_URL);
auth.setScopes(scopes);
auth.setScopes(SCOPES);
auth = service.createAuthorization(auth);
return auth != null ? auth.getToken() : null;
}
Expand All @@ -167,13 +165,12 @@ public Bundle getAuthToken(final AccountAuthenticatorResponse response,
DefaultClient client = new DefaultClient();
client.setCredentials(account.name, password);
OAuthService service = new OAuthService(client);
List<String> scopes = Arrays.asList("repo", "user", "gist");

String authToken;
try {
authToken = getAuthorization(service, scopes);
authToken = getAuthorization(service);
if (TextUtils.isEmpty(authToken))
authToken = createAuthorization(service, scopes);
authToken = createAuthorization(service);
} catch (IOException e) {
Log.e(TAG, "Authorization retrieval failed", e);
throw new NetworkErrorException(e);
Expand Down
78 changes: 63 additions & 15 deletions app/src/main/java/com/github/mobile/accounts/LoginActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@
import static android.view.KeyEvent.ACTION_DOWN;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static com.github.mobile.accounts.AccountConstants.ACCOUNT_TYPE;
import static com.github.mobile.accounts.AccountConstants.PROVIDER_AUTHORITY;
import static com.github.mobile.accounts.AccountConstants.*;
import static com.github.mobile.RequestCodes.OTP_CODE_ENTER;
import static com.github.mobile.accounts.TwoFactorAuthActivity.PARAM_EXCEPTION;
import static com.github.mobile.accounts.TwoFactorAuthClient.TWO_FACTOR_AUTH_TYPE_SMS;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.AlertDialog;
Expand Down Expand Up @@ -59,7 +62,6 @@
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.github.kevinsawicki.wishlist.ViewFinder;
import com.github.mobile.DefaultClient;
import com.github.mobile.R.id;
import com.github.mobile.R.layout;
import com.github.mobile.R.menu;
Expand All @@ -77,6 +79,7 @@

import org.eclipse.egit.github.core.User;
import org.eclipse.egit.github.core.client.GitHubClient;
import org.eclipse.egit.github.core.service.OAuthService;
import org.eclipse.egit.github.core.service.UserService;

import roboguice.util.RoboAsyncTask;
Expand Down Expand Up @@ -105,7 +108,7 @@ public class LoginActivity extends RoboSherlockAccountAuthenticatorActivity {
*/
private static final long SYNC_PERIOD = 8L * 60L * 60L;

private static void configureSyncFor(Account account) {
public static void configureSyncFor(Account account) {
Log.d(TAG, "Configuring account sync");

ContentResolver.setIsSyncable(account, PROVIDER_AUTHORITY, 1);
Expand All @@ -114,7 +117,7 @@ private static void configureSyncFor(Account account) {
new Bundle(), SYNC_PERIOD);
}

private static class AccountLoader extends
public static class AccountLoader extends
AuthenticatedUserTask<List<User>> {

@Inject
Expand Down Expand Up @@ -198,6 +201,7 @@ public void afterTextChanged(Editable gitDirEditText) {

passwordText.setOnKeyListener(new OnKeyListener() {

@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event != null && ACTION_DOWN == event.getAction()
&& keyCode == KEYCODE_ENTER && loginEnabled()) {
Expand All @@ -210,6 +214,7 @@ public boolean onKey(View v, int keyCode, KeyEvent event) {

passwordText.setOnEditorActionListener(new OnEditorActionListener() {

@Override
public boolean onEditorAction(TextView v, int actionId,
KeyEvent event) {
if (actionId == IME_ACTION_DONE && loginEnabled()) {
Expand Down Expand Up @@ -305,9 +310,19 @@ public void onCancel(DialogInterface dialog) {

@Override
public User call() throws Exception {
GitHubClient client = new DefaultClient();
GitHubClient client = new TwoFactorAuthClient();
client.setCredentials(username, password);
User user = new UserService(client).getUser();

User user;
try {
user = new UserService(client).getUser();
} catch (TwoFactorAuthException e) {
if (e.twoFactorAuthType == TWO_FACTOR_AUTH_TYPE_SMS)
sendSmsOtpCode(new OAuthService(client));
openTwoFactorAuthActivity();

return null;
}

Account account = new Account(user.getLogin(), ACCOUNT_TYPE);
if (requestNewAccount) {
Expand All @@ -330,24 +345,37 @@ protected void onException(Exception e) throws RuntimeException {
dialog.dismiss();

Log.d(TAG, "Exception requesting authenticated user", e);

if (AccountUtils.isUnauthorized(e))
onAuthenticationResult(false);
else
ToastUtils.show(LoginActivity.this, e,
string.connection_failed);
handleLoginException(e);
}

@Override
public void onSuccess(User user) {
dialog.dismiss();

onAuthenticationResult(true);
if (user != null)
onAuthenticationResult(true);
}
};
authenticationTask.execute();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);

if (requestCode == OTP_CODE_ENTER) {
switch (resultCode) {
case RESULT_OK:
onAuthenticationResult(true);
break;
case RESULT_CANCELED:
Exception e = (Exception) data.getExtras().getSerializable(PARAM_EXCEPTION);
handleLoginException(e);
break;
}
}
}

/**
* Called when response is received from the server for confirm credentials
* request. See onAuthenticationResult(). Sets the
Expand Down Expand Up @@ -406,6 +434,7 @@ public void onAuthenticationResult(boolean result) {
}
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case id.m_login:
Expand All @@ -432,4 +461,23 @@ private List<String> getEmailAddresses() {
addresses.add(account.name);
return addresses;
}
}

private void sendSmsOtpCode(final OAuthService service) throws IOException {
try {
AccountAuthenticator.createAuthorization(service);
} catch (TwoFactorAuthException ignored) {
}
}

private void openTwoFactorAuthActivity() {
Intent intent = TwoFactorAuthActivity.createIntent(this, username, password);
startActivityForResult(intent, OTP_CODE_ENTER);
}

private void handleLoginException(final Exception e) {
if (AccountUtils.isUnauthorized(e))
onAuthenticationResult(false);
else
ToastUtils.show(LoginActivity.this, e, string.connection_failed);
}
}
Loading

0 comments on commit d6ba3f9

Please sign in to comment.