Skip to content

Commit

Permalink
Enable manual token entry
Browse files Browse the repository at this point in the history
  • Loading branch information
npmccallum committed Oct 18, 2013
1 parent 4013a12 commit d40daba
Show file tree
Hide file tree
Showing 9 changed files with 427 additions and 55 deletions.
162 changes: 162 additions & 0 deletions res/layout/manual.xml
@@ -0,0 +1,162 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp" >

<TableRow
android:layout_width="match_parent"
android:layout_height="48dp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical|right"
android:text="@string/interval"
android:paddingRight="8dp" />

<EditText
android:id="@+id/issuer"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:hint="jdoe@example.com"
android:textAppearance="?android:attr/textAppearanceSmallInverse" />
</TableRow>

<TableRow
android:layout_width="match_parent"
android:layout_height="48dp" >

<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical|right"
android:text="@string/id"
android:paddingRight="8dp" />

<EditText
android:id="@+id/id"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:hint="18c5d06cfcbd4927"
android:textAppearance="?android:attr/textAppearanceSmallInverse" />
</TableRow>

<TableRow
android:layout_width="match_parent"
android:layout_height="48dp" >

<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical|right"
android:text="@string/secret"
android:paddingRight="8dp" />

<EditText
android:id="@+id/secret"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:digits="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ234567="
android:hint="Base32 (ex. &apos;GEZDGNBV&apos;)"
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>

<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="4dp"
android:background="?android:attr/dividerHorizontal"
/>

<TableRow
android:layout_width="match_parent"
android:layout_height="48dp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical|right"
android:text="@string/type"
android:paddingRight="8dp" />

<Spinner
android:id="@+id/type"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:entries="@array/token_types" />
</TableRow>

<TableRow
android:layout_width="match_parent"
android:layout_height="48dp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical|right"
android:text="@string/algorithm"
android:paddingRight="8dp" />

<Spinner
android:id="@+id/algorithm"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:entries="@array/algorithms" />
</TableRow>

<TableRow
android:layout_width="match_parent"
android:layout_height="48dp" >
<TextView
android:id="@+id/interval_label"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical|right"
android:text="@string/interval"
android:paddingRight="8dp" />

<EditText
android:id="@+id/interval"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:inputType="number"
android:text="30" />
</TableRow>

<TableRow
android:layout_width="match_parent"
android:layout_height="48dp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical|right"
android:text="@string/digits"
android:paddingRight="8dp" />

<RadioGroup
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal" >
<RadioButton
android:id="@+id/digits6"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="6"
android:checked="true" />

<RadioButton
android:id="@+id/digits8"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="8" />
</RadioGroup>
</TableRow>
</TableLayout>
2 changes: 1 addition & 1 deletion res/menu/main.xml
Expand Up @@ -27,5 +27,5 @@
android:orderInCategory="100"
android:showAsAction="always"
android:icon="@drawable/scan"
android:title="@string/action_add" />
android:title="@string/add" />
</menu>
30 changes: 28 additions & 2 deletions res/values/strings.xml
@@ -1,13 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">FreeOTP</string>
<string name="action_add">Add</string>
<string name="token_scan_invalid">The scanned token data was invalid!</string>
<string name="add">Add</string>
<string name="invalid_token">The token specified was invalid!</string>
<string name="delete_message">Are you sure you want to remove this token?\n\nNOTE: This will NOT disable two-factor authentication on the server.\n\n</string>
<string name="delete">Delete</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="install_title">Install Barcode Scanner?</string>
<string name="install_message">Barcode Scanner is required. Would you like to install it?</string>
<string name="no_keys">No OTP keys installed.</string>
<string name="add_token">Add Token</string>
<string name="scan_qr_code">Scan QR Code</string>
<string name="interval">Interval</string>
<string name="counter">Counter</string>
<string name="id">ID</string>
<string name="secret">Secret</string>
<string name="type">Type</string>
<string name="algorithm">Algorithm</string>
<string name="digits">Digits</string>

<string-array name="token_types">
<item>Time-based (TOTP)</item>
<item>Counter-based (HOTP)</item>
</string-array>

<string-array name="digits">
<item>6</item>
<item>8</item>
</string-array>

<string-array name="algorithms">
<item>MD5</item>
<item>SHA1</item>
<item>SHA256</item>
<item>SHA512</item>
</string-array>
</resources>
96 changes: 96 additions & 0 deletions src/org/fedorahosted/freeotp/AddTokenDialog.java
@@ -0,0 +1,96 @@
package org.fedorahosted.freeotp;

import java.util.Locale;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.Spinner;
import android.widget.TextView;

public abstract class AddTokenDialog extends AlertDialog {
private final int SHA1_OFFSET = 1;
private final int TOTP_OFFSET = 0;

public AddTokenDialog(Context ctx) {
super(ctx);

setTitle(R.string.add_token);
setView(getLayoutInflater().inflate(R.layout.manual, null));

setButton(BUTTON_NEGATIVE, ctx.getString(android.R.string.cancel), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});

setButton(BUTTON_POSITIVE, ctx.getString(R.string.add), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Get the fields
String issuer = Uri.encode(((EditText) findViewById(R.id.issuer)).getText().toString());
String id = Uri.encode(((EditText) findViewById(R.id.id)).getText().toString());
String secret = Uri.encode(((EditText) findViewById(R.id.secret)).getText().toString());
String type = ((Spinner) findViewById(R.id.type)).getSelectedItemId() == TOTP_OFFSET ? "totp" : "hotp";
String algorithm = ((Spinner) findViewById(R.id.algorithm)).getSelectedItem().toString().toLowerCase(Locale.US);
int interval = Integer.parseInt(((EditText) findViewById(R.id.interval)).getText().toString());
int digits = ((RadioButton) findViewById(R.id.digits6)).isChecked() ? 6 : 8;

// Create the URI
String uri = String.format(Locale.US, "otpauth://%s/%s:%s?secret=%s&algorithm=%s&digits=%d",
type, issuer, id, secret, algorithm, digits);
if (type.equals("totp"))
uri = uri.concat(String.format("&period=%d", interval));
else
uri = uri.concat(String.format("&counter=%d", interval));

// Add the token
addToken(uri);
}
});
}

@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();

// Disable the Add button
getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);

// Set constraints on when the Add button is enabled
((EditText) findViewById(R.id.issuer)).addTextChangedListener(new AddTokenTextWatcher(this));
((EditText) findViewById(R.id.id)).addTextChangedListener(new AddTokenTextWatcher(this));
((EditText) findViewById(R.id.secret)).addTextChangedListener(new AddTokenSecretTextWatcher(this));
((EditText) findViewById(R.id.interval)).addTextChangedListener(new AddTokenTextWatcher(this));

// Select the default algorithm
((Spinner) findViewById(R.id.algorithm)).setSelection(SHA1_OFFSET);

// Setup the Interval / Counter toggle
((Spinner) findViewById(R.id.type)).setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position == 0) {
((TextView) findViewById(R.id.interval_label)).setText(R.string.interval);
((EditText) findViewById(R.id.interval)).setText("30");
} else {
((TextView) findViewById(R.id.interval_label)).setText(R.string.counter);
((EditText) findViewById(R.id.interval)).setText("0");
}
}

@Override
public void onNothingSelected(AdapterView<?> parent) {

}
});

}

public abstract void addToken(String uri);
}
27 changes: 27 additions & 0 deletions src/org/fedorahosted/freeotp/AddTokenSecretTextWatcher.java
@@ -0,0 +1,27 @@
package org.fedorahosted.freeotp;

import android.app.AlertDialog;
import android.text.Editable;

public class AddTokenSecretTextWatcher extends AddTokenTextWatcher {
public AddTokenSecretTextWatcher(AlertDialog dialog) {
super(dialog);
}

@Override
public void afterTextChanged(Editable s) {
super.afterTextChanged(s);

if (s.length() == 0)
return;

boolean haveData = false;
for (int i = s.length() - 1; i >= 0; i--) {
char c = s.charAt(i);
if (c != '=')
haveData = true;
else if (haveData)
s.delete(i, i + 1);
}
}
}
47 changes: 47 additions & 0 deletions src/org/fedorahosted/freeotp/AddTokenTextWatcher.java
@@ -0,0 +1,47 @@
package org.fedorahosted.freeotp;

import android.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.Button;
import android.widget.EditText;

public class AddTokenTextWatcher implements TextWatcher {
private final AlertDialog dialog;

public AddTokenTextWatcher(AlertDialog dialog) {
this.dialog = dialog;
}

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Button b = dialog.getButton(AlertDialog.BUTTON_POSITIVE);

b.setEnabled(false);

if (((EditText) dialog.findViewById(R.id.issuer)).getText().length() == 0)
return;

if (((EditText) dialog.findViewById(R.id.id)).getText().length() == 0)
return;

if (((EditText) dialog.findViewById(R.id.secret)).getText().length() == 0 ||
((EditText) dialog.findViewById(R.id.secret)).getText().length() % 8 != 0)
return;

if (((EditText) dialog.findViewById(R.id.interval)).getText().length() == 0)
return;

b.setEnabled(true);
}

@Override
public void afterTextChanged(Editable s) {

}
}

0 comments on commit d40daba

Please sign in to comment.