/* * Copyright 2014 sonaive.com. All rights reserved. * * 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. */ package com.sonaive.v2ex.util; import android.app.Activity; import android.content.Context; import com.google.gson.JsonObject; import com.sonaive.v2ex.sync.api.Api; import com.sonaive.v2ex.R; import com.sonaive.v2ex.sync.api.UserIdentityApi; import com.squareup.okhttp.Callback; import com.squareup.okhttp.MediaType; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.Response; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.sonaive.v2ex.util.LogUtils.LOGD; import static com.sonaive.v2ex.util.LogUtils.LOGW; import static com.sonaive.v2ex.util.LogUtils.makeLogTag; /** * This helper handles the UI flow for signing in an account. It handles * connecting to the V2EX API to fetch profile data (name, cover photo, etc). * The life of this object is tied to an Activity. Do not attempt to share * it across Activities, as unhappiness will result. * * Created by liutao on 12/6/14. */ public class LoginHelper { private static final String TAG = makeLogTag(LoginHelper.class); Context mAppContext; // The Activity this object is bound to (we use a weak ref to avoid context leaks) WeakReference<Activity> mActivityRef; // Callbacks interface we invoke to notify the user of this class of useful events WeakReference<Callbacks> mCallbacksRef; // Name of the account to log in as String mAccountName; // account password String mPassword; // Are we in the started state? Started state is between onStart and onStop. boolean mStarted = false; UserIdentityApi mUserIdentityApi; private OkHttpClient okHttpClient; public interface Callbacks { void onIdentityCheckedSuccess(JsonObject result); void onIdentityCheckedFailed(JsonObject result); void onNodeCollectionFetchedSuccess(JsonObject result); void onNodeCollectionFetchedFailed(JsonObject result); } public LoginHelper(Activity activity, Callbacks callbacks, String accountName, String password) { LOGD(TAG, "Helper created. Account: " + accountName); mActivityRef = new WeakReference<>(activity); mCallbacksRef = new WeakReference<>(callbacks); mAppContext = activity.getApplicationContext(); mAccountName = accountName; mPassword = password; mUserIdentityApi = new UserIdentityApi(mAppContext); } public boolean isStarted() { return mStarted; } public String getAccountName() { return mAccountName; } private Activity getActivity(String methodName) { Activity activity = mActivityRef.get(); if (activity == null) { LOGD(TAG, "Helper lost Activity reference, ignoring (" + methodName + ")"); } return activity; } /** Starts the helper. Call this from your Activity's onStart(). */ public void start() { Activity activity = getActivity("start()"); if (activity == null) { return; } if (mStarted) { LOGW(TAG, "Helper already started. Ignoring redundant call."); return; } mStarted = true; LOGD(TAG, "Helper starting. Connecting " + mAccountName); if (okHttpClient == null) { okHttpClient = new OkHttpClient(); } mUserIdentityApi.verifyUserIdentity(mAccountName, mCallbacksRef); } /** Stop the helper. Call this from your Activity's onStop(). */ public void stop() { if (!mStarted) { LOGW(TAG, "Helper already stopped. Ignoring redundant call."); return; } LOGD(TAG, "Helper stopping."); mStarted = false; } /** After spent hours digging, I give up */ private void signIn(String onceCode) { Map<String, String> params = new HashMap<>(); params.put("next", "/"); params.put("u", mAccountName); params.put("p", mPassword); params.put("once", onceCode); RequestBody postBody = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), params.toString()); Request request = new Request.Builder() .header("Origin", "http://www.v2ex.com") .header("Referer", "http://www.v2ex.com/signin") .header("X-Requested-With", "com.android.browser") .header("Cache-Control", "max-age=0") .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") .header("Accept-Language", "zh-CN, en-US") .header("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7") .url(Api.API_URLS.get(Api.API_SIGNIN)) .post(postBody) .build(); try { okHttpClient.setFollowRedirects(false); Response response = okHttpClient.newCall(request).execute(); final JsonObject result = new JsonObject(); Pattern errorPattern = Pattern.compile("<div class=\"problem\">(.*)</div>"); Matcher errorMatcher = errorPattern.matcher(response.body().string()); final String errorContent; if (response.code() == 302) {// temporary moved, 302 found, disallow redirects. LOGD(TAG, "sign in success!"); getUserInfo(); return; } else if (errorMatcher.find()) { errorContent = errorMatcher.group(1).replaceAll("<[^>]+>", ""); } else { errorContent = "Unknown error"; } if (errorContent != null) { result.addProperty("result", "fail"); result.addProperty("err_msg", errorContent); LOGD(TAG, "sign in error, err_msg = " + errorContent); } } catch (IOException e) { e.printStackTrace(); } } /** Get once code for sign in */ private void getOnceCodeAndSignin() { Request request = new Request.Builder() .url(Api.API_URLS.get(Api.API_SIGNIN)) .build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { final JsonObject result = new JsonObject(); result.addProperty("result", "fail"); Activity activity = getActivity("getOnceCodeAndSignin()"); if (activity == null) { return; } if (e != null) { result.addProperty("err_msg", activity.getString(R.string.err_io_exception)); } else { result.addProperty("err_msg", activity.getString(R.string.err_get_once_failed)); } } @Override public void onResponse(Response response) throws IOException { final JsonObject result = new JsonObject(); Pattern pattern = Pattern.compile("<input type=\"hidden\" value=\"([0-9]+)\" name=\"once\" />"); final Matcher matcher = pattern.matcher(response.body().string()); Activity activity = getActivity("getOnceCodeAndSignin()"); if (activity == null) { return; } if (matcher.find()) { String code = matcher.group(1); signIn(code); } else { result.addProperty("result", "fail"); result.addProperty("err_msg", activity.getString(R.string.err_get_once_failed)); } } }); } /** Get signed in account info(node collections, account name), need cookie */ private void getUserInfo() { Request request = new Request.Builder() .url(Api.API_URLS.get(Api.API_MY_NODES)) .build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { JsonObject result = new JsonObject(); result.addProperty("result", "fail"); result.addProperty("err_msg", mAppContext.getString(R.string.err_get_node_collections_failed)); if (mCallbacksRef != null) { mCallbacksRef.get().onNodeCollectionFetchedFailed(result); } } @Override public void onResponse(Response response) throws IOException { JsonObject result = new JsonObject(); if (response.code() == 200) { Pattern userPattern = Pattern.compile("<a href=\"/member/([^\"]+)\" class=\"top\">"); Matcher userMatcher = userPattern.matcher(response.body().string()); if (userMatcher.find()) { String accountName = userMatcher.group(1); Pattern collectionPattern = Pattern.compile("</a>  <a href=\"/go/([^\"]+)\">"); Matcher collectionMatcher = collectionPattern.matcher(response.body().string()); List<String> collections = new ArrayList<>(); if (collectionMatcher.find()) { collections.add(collectionMatcher.group(1)); while (collectionMatcher.find()) { collections.add(collectionMatcher.group(1)); } } // TODO add the user node collections into database LOGD(TAG, "Get user: " + accountName + " node collections: " + collections.toString()); result.addProperty("result", "ok"); if (mCallbacksRef != null) { mCallbacksRef.get().onNodeCollectionFetchedSuccess(result); } } else { result.addProperty("result", "fail"); result.addProperty("err_msg", mAppContext.getString(R.string.err_get_my_nodes_failed)); if (mCallbacksRef != null) { mCallbacksRef.get().onNodeCollectionFetchedFailed(result); } } } else if (response.code() == 403) { result.addProperty("result", "fail"); result.addProperty("err_msg", "403 Forbidden"); if (mCallbacksRef != null) { mCallbacksRef.get().onIdentityCheckedFailed(result); } } else { result.addProperty("result", "fail"); result.addProperty("err_msg", mAppContext.getString(R.string.err_unknown_error)); if (mCallbacksRef != null) { mCallbacksRef.get().onIdentityCheckedFailed(result); } } LOGD(TAG, "responseCode: " + response.code() + ", result: " + result.get("result") + ", err_msg: " + result.get("err_msg")); } }); } }