package com.teamluper.luper; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.support.v4.app.Fragment; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.widget.EditText; import android.widget.RelativeLayout; import android.widget.TextView; import com.facebook.Request; import com.facebook.Response; import com.facebook.Session; import com.facebook.SessionLoginBehavior; import com.facebook.SessionState; import com.facebook.UiLifecycleHelper; import com.facebook.model.GraphUser; import com.facebook.widget.LoginButton; import com.googlecode.androidannotations.annotations.Background; import com.googlecode.androidannotations.annotations.EFragment; import com.googlecode.androidannotations.annotations.UiThread; import com.googlecode.androidannotations.annotations.rest.RestService; import com.teamluper.luper.rest.LuperRestClient; import org.json.JSONException; import org.json.JSONObject; import java.security.MessageDigest; import java.util.Arrays; @EFragment public class TabLoginFragment extends Fragment { // Values for email and password at the time of the login attempt. private String mEmail; private String mPassword; // UI references. private EditText mEmailView; private EditText mPasswordView; private View mLoginFormView; private View mLoginStatusView; private TextView mLoginStatusMessageView; private SQLiteDataSource dataSource; @RestService LuperRestClient rest; private static final int HASH_COUNT = 100; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((LuperLoginActivity) getActivity()).loginFragment = this; uiHelper = new UiLifecycleHelper(getActivity(), callback); uiHelper.onCreate(savedInstanceState); } @Override public void onResume() { super.onResume(); // Gets called when login activity gets called and session // is not null Session session = Session.getActiveSession(); if (session != null && (session.isOpened() || session.isClosed()) ) { onSessionStateChange(session, session.getState(), null); } uiHelper.onResume(); showProgress(false); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); uiHelper.onActivityResult(requestCode, resultCode, data); } @Override public void onPause() { super.onPause(); uiHelper.onPause(); } @Override public void onDestroy() { super.onDestroy(); uiHelper.onDestroy(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); uiHelper.onSaveInstanceState(outState); } @Override public View onCreateView(LayoutInflater infl, ViewGroup vg, Bundle state) { if(vg == null) return null; View v = infl.inflate(R.layout.tab_login_layout, vg, false); Activity a = getActivity(); // set up the SQLite database access. dataSource = ((LuperLoginActivity)a).getDataSource(); if(!dataSource.isOpen()) dataSource.open(); // Set up the login form. mEmail = a.getIntent().getStringExtra("luperPrefilledEmail"); mEmailView = (EditText) v.findViewById(R.id.login_email); mEmailView.setText(mEmail); mPasswordView = (EditText) v.findViewById(R.id.login_password); mPasswordView .setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { if (id == R.id.login || id == EditorInfo.IME_NULL) { attemptLogin(); return true; } return false; } }); // Give facebook loginbutton email permissions LoginButton authButton = (LoginButton) v.findViewById(R.id.authButton); // TODO - help luper use facebook app without failing. until then // This line should prevent Luper from launching the facebook app for login, // and use the web splash instead. authButton.setLoginBehavior(SessionLoginBehavior.SUPPRESS_SSO); authButton.setFragment(this); authButton.setReadPermissions(Arrays.asList("email")); mLoginFormView = v.findViewById(R.id.login_form); mLoginStatusView = v.findViewById(R.id.login_status); mLoginStatusMessageView = (TextView) v.findViewById(R.id.login_status_message); showProgress(false); v.findViewById(R.id.login_button).setOnClickListener( new View.OnClickListener() { @Override public void onClick(View view) { attemptLogin(); } }); return v; } //Updates the Luper interface once usr is logged into Facebook. private static final String TAG = "LoginFragment"; private Session.StatusCallback callback = new Session.StatusCallback() { @Override public void call(Session sesh, SessionState seshState, Exception e) { onSessionStateChange(sesh,seshState,e); } }; private UiLifecycleHelper uiHelper; protected void onSessionStateChange(Session sesh, SessionState seshState, Exception e) { final LuperLoginActivity_ a = (LuperLoginActivity_)getActivity(); if (seshState.isOpened()) { /** usr logged in **/ // Create new request to facebook API Request.executeMeRequestAsync(sesh, new Request.GraphUserCallback() { // callback after Graph API response with user object @Override public void onCompleted(GraphUser user, Response response) { if (user != null) { //loadActiveSession(user); // TextView welcome = (TextView) findViewById(R.id.welcome); // welcome.setText("Hello " + user.getName() + "!"); // Let's snag the user's email address to create a unique ID for Mike's DB // updates the header on the login tab // If email doesn't work, let's use the GraphUser's facebook link as unique ID // naturally each link is associated with at most one user TextView header_login = (TextView) a.findViewById(R.id.header_login); header_login.setText(user.getName() + ", " + user.getUsername() + ", " + user.getId() + ", " + user.getLink() + ", " + user.getFirstName()+ user.asMap().get("email")); } a.completeFacebookLogin(user.asMap().get("email").toString(),user.getUsername()); // this shouldn't need to be called. but for some reason LuperLogin isn't switching // over to Main automatically. //a.startMainActivity(); } }); } else if (seshState.isClosed()) { // usr logged out TextView header_login = (TextView) a.findViewById(R.id.header_login); header_login.setText("Come back soon!"); } else { // probably shouldn't be here } } // @Override // public void onResume() { // super.onResume(); // showProgress(false); // } /** * Attempts to sign in or register the account specified by the login form. If * there are form errors (invalid email, missing fields, etc.), the errors are * presented and no actual login attempt is made. */ @UiThread public void attemptLogin() { Activity a = getActivity(); if(!LuperMainActivity.deviceIsOnline(a)) { DialogFactory.alert(a, "Login Failed!", "You are not connected to the internet! " + "You must be online to log in. Once logged in, however, you can use Lüper while offline."); } // Reset errors. mEmailView.setError(null); mPasswordView.setError(null); // Store values at the time of the login attempt. mEmail = mEmailView.getText().toString(); mPassword = mPasswordView.getText().toString(); boolean cancel = false; View focusView = null; // Check for a valid password. if (TextUtils.isEmpty(mPassword)) { mPasswordView.setError(getString(R.string.error_field_required)); focusView = mPasswordView; cancel = true; } else if (mPassword.length() < 4) { mPasswordView.setError(getString(R.string.error_invalid_password)); focusView = mPasswordView; cancel = true; } // Check for a valid email address. if (TextUtils.isEmpty(mEmail)) { mEmailView.setError(getString(R.string.error_field_required)); focusView = mEmailView; cancel = true; } else if (!mEmail.contains("@")) { mEmailView.setError(getString(R.string.error_invalid_email)); focusView = mEmailView; cancel = true; } if (cancel) { // There was an error; don't attempt login and focus the first // form field with an error. focusView.requestFocus(); } else { // Show a progress spinner, and kick off a background task to // perform the user login attempt. mLoginStatusMessageView.setText(R.string.progress_logging_in); showProgress(true); completeLoginAttempt(mEmail, mPassword); } } @Background public void completeLoginAttempt(String email, String password) { // first, fetch a challenge salt to hash the password with. try { JSONObject challengeRequest = new JSONObject(); challengeRequest.put("email", email); String challengeResponseJSON = rest.getLoginChallengeSalt(challengeRequest.toString()); JSONObject challengeResponse = new JSONObject(challengeResponseJSON); if(!isResponseSuccessful(challengeResponse)) { loginFailure(challengeResponse.getString("message")); return; } // now we take the challenge salt and compute the login attempt hash. String salt = challengeResponse.getString("salt"); String attemptHash = saltyHashBrowns(sha256(password), salt, HASH_COUNT); // assemble the actual login attempt request JSONObject loginRequest = new JSONObject(); loginRequest.put("email", email); loginRequest.put("attemptHash", attemptHash); String loginResponseJSON = rest.validateLoginAttempt(loginRequest.toString()); JSONObject loginResponse = new JSONObject(loginResponseJSON); if(!isResponseSuccessful(loginResponse)) { loginFailure(loginResponse.getString("message")); return; } if(!loginResponse.getBoolean("isLoginValid")) { loginFailure("The email address or password you entered was invalid!"); return; } User existingUser = dataSource.getUserByEmail(email); if(existingUser == null) { // we need to create a user first, matching the user from the server String userFromServerJSON = rest.fetchUserByEmail(email); JSONObject userFromServer = new JSONObject(userFromServerJSON); existingUser = dataSource.createUser(userFromServer.getLong("_id"),userFromServer.getString("username"),email); } dataSource.setActiveUser(existingUser); loginSuccess(); } catch(JSONException e) { Log.e("luper","=== JSON Exception while attempting to validate login === Exception: ", e); loginFailure("Something went wrong while logging in!"); return; } } @UiThread public void loginSuccess() { if(mEmailView != null) mEmailView.setText(""); if(mPasswordView != null) mPasswordView.setText(""); Intent intent = new Intent(getActivity(), LuperMainActivity_.class); startActivity(intent); } @UiThread public void loginFailure(String errorMessage) { showProgress(false); DialogFactory.alert(getActivity(), "Login Failed!", errorMessage); } public boolean isResponseSuccessful(JSONObject response) { try { return response.getBoolean("success"); } catch(Exception e) { Log.e("luper","=== A REGISTRATION API RESPONSE WAS UNSUCCESSFUL === Exception: ", e); return false; } } private String sha256(String password) { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(password.getBytes("UTF-8")); // Change this to "UTF-16" if needed byte[] digest = md.digest(); return bytesToHex(digest); } catch(Exception e) { Log.e("luper", "=== ERROR COMPUTING SHA256 LOGIN ATTEMPT PASSWORD HASH === Exception: ", e); return ""; } } public static String bytesToHex(byte[] bytes) { final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; char[] hexChars = new char[bytes.length * 2]; int v; for ( int j = 0; j < bytes.length; j++ ) { v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } public String saltyHashBrowns(String simpleHashedPassword, String salt, int hashCount) { // Prefix the password with the salt String hash = salt + simpleHashedPassword; // Hashing our hashes times a hundred thousand, for security! for(int i=0; i<hashCount; i++) { hash = sha256(hash); // hashity hash } return hash; } public void showProgressSpinner(boolean show) { showProgress(show); } /** * Shows the progress UI and hides the login form. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) private void showProgress(final boolean show) { // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow // for very easy animations. If available, use these APIs to fade-in // the progress spinner. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { int shortAnimTime = getResources().getInteger( android.R.integer.config_shortAnimTime); mLoginStatusView.setVisibility(View.VISIBLE); mLoginStatusView.animate().setDuration(shortAnimTime).alpha(show ? 1 : 0) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE); } }); mLoginFormView.setVisibility(View.VISIBLE); mLoginFormView.animate().setDuration(shortAnimTime).alpha(show ? 0 : 1) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); } }); } else { // The ViewPropertyAnimator APIs are not available, so simply show // and hide the relevant UI components. mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE); mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); } } }