Połączenie androida z wordpressem – WP Rest Api, oAuth 2.0, Scribe

WordPress jest jednym z najpopularniejszych CMS na świecie, szacuje się że około 27,5% strona w internecie jest stworzonych właśnie w oparciu o ten system. Toteż tworząc aplikacje mobilną nierzadko będziemy musieli wiedzieć jak współpracować z stronami opartymi o WordPressa.

 
Zanim zaczniemy cokolwiek pisząc na androida zalogujmy się do WordPressa i zainstalujmy wtyczkę „WP REST API”. Wtyczka ta rozszerza możliwości WordPressa o łatwy dostęp do zasobów poprzez odpowiedni URI oraz protokół HTTP.
 
rest api

Jeśli wszystko zainstalowaliśmy prawidłowo pod adresem: http://binaryalchemist.pl/wp-json/wp/v2/posts powinniśmy otrzymać w Jsonie ostatnio opublikowane na stronie posty. Podobnie adres http://binaryalchemist.pl/wp-json/wp/v2/comments zwróci nam ostatnie komentarze.
 
Kiedy to mamy już załatwione możemy zając się przygotowaniem naszej aplikacji która współpracowałaby z wordpresem:
 

public class MainActivity extends AppCompatActivity {
    
    ListView listView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
         listView = (ListView) findViewById(R.id.listView);
        new getJSONAsyncTask().execute();

    }

private class getJSONAsyncTask extends AsyncTask<String, Void, String> {
    @Override
    protected void onPreExecute() {
    }
    @Override
    protected void onPostExecute(String result) {
        try {
            JSONArray jsonArray = new JSONArray(result);
            ArrayList<String> titleList = new ArrayList<String>();
            for (int num = 0; num < jsonArray.length(); num++) {
                JSONObject postObject = (JSONObject) jsonArray.get(num);
                JSONObject titleObject = (JSONObject) postObject.get("title");
                titleList.add(titleObject.getString("rendered"));
            }
            ArrayAdapter adapter = new ArrayAdapter(getApplicationContext(),
                    android.R.layout.simple_list_item_1, titleList);
            listView.setAdapter(adapter);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    protected String doInBackground(String... params) {
        String result = getJsonFromHttp("http://binaryalchemist.pl/wp-json/wp/v2/posts?per_page=50");
        return result;
    }
}
public String getJsonFromHttp(String jsonUrl) {
    URL url;
    String responseData = null;
    try {
        url = new URL(jsonUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setReadTimeout(10000);
        conn.setConnectTimeout(15000);
        conn.setRequestMethod("GET");
        conn.setAllowUserInteraction(false);
        conn.connect();
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line;
        while ((line = bufferedReader.readLine()) != null) {
            stringBuilder.append(line + "\n");
        }
        bufferedReader.close();
        responseData = stringBuilder.toString();
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return responseData;
}
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="binaryalchemist.wordpressconnector.MainActivity"
    android:background="#428bca">
    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    </ListView>

</android.support.constraint.ConstraintLayout>

Jak już mamy zainstalowaną wtyczkę musimy jedynie połączyć się z interesującą nasz stroną i pobrać jej zasoby w formie jsona, korzystamy przy tym z zadania asynchronicznego aby nie blokować urządzenia. W moim przypadku zdecydowałem się na pobranie tytułów wszystkich stworzonych postów na blogu i wyświetleniu ich w postaci listview. Wynik jest następujący:
 
api result

W ten sposób możemy szybko zdobyć interesujące nas informacje z serwera na którym działa WordPress. Problemy mogą zacząć się jednak kiedy potrzebujemy dostępu do zasobów wymagających autoryzacji.
 

oAuth 2.0

 
Wielokrotnie będziemy mieli sytuacje w której nie chcemy by każdy użytkownik miał dostęp do wszystkich informacji jakie udostępnia nasz serwer. Już domyślnie w wtyczce wp rest api dostęp do poprzednich wersji artykułu jest zablokowany dla nie autoryzowanych użytkowników. Wejście pod adres http://binaryalchemist.pl/wp-json/wp/v2/posts/{id}/revisions zwróci nam status 401 czyli brak uprawnień.
 
Istnieje kilka rodzajów autoryzacji najprostszą jest przesyłanie loginu i hasła w url lecz zalecana i bardziej bezpieczna metoda to autoryzacji przy użyciu protokołu oAuth dlatego też z niej tutaj skorzystamy. Protokół z jakiego będziemy korzystać to oAuth 2.0
 
Dzięki oAuth możemy współpracować z naszą stroną WordPressową bez konieczności zapisywania poufnych informacji jak login czy hasło. Na początku dobrze by było zrozumieć jak autoryzacja oAuth przebiega, pomóc może może w tym poniższy diagram sekwencji:
 
flow

Na początku rejestrujemy nasza aplikacja w serwerze uzyskując dla niej unikalny numer identyfikacyjny, app secret oraz definiujemy stronę zwrotną gdzie będziemy pobierać kod niezbędny do stworzenia access token. Kiedy chcemy uzyskać dostęp do zasobów wysyłamy żądanie do serwera w celu naszej autoryzacji. Logujemy się do wordpressa. Jeśli to nam się uda zostajemy przekierowani do wcześniej zdefiniowanej strony zwrotnej gdzie uzyskamy kod do wygenerowania request tokena. Wysyłamy ponownie żądanie mające na celu wygenerować nasz access token podając nasz unikalny identyfikator, request token oraz parametr grant_type. Jeśli wszystko pójdzie prawidłowo będziemy mogli uzyskać dostęp do wybranych zasobów korzystając z dopiero co stworzonego access tokena. Token ten możemy używać dopóki jego ważność nie wygaśnie.
 
Całość może wydawać się dość skomplikowana ale ogólna praca polega w dużym stopniu na wysłaniu żądania do naszego serwera aby otrzymać request token. Żądanie takie wygląda następująco:
http://binaryalchemist.pl/?oauth=authorize&client_id=our_client_id&response_type=code
 
gdzie:
http://binaryalchemist.pl/?oauth=authorize domyślny adres (endpoint) odpoweidzialny za generowanie request tokena
client_id – numer identyfikacyjny aplikacji dostępny w panelu wordpressa
response_type – nazwa zmiennej pod jaką będzie generowany request token (w tym wypadku nazwa to code)
 
Jeśli wszystko pójdzie dobrze uzyskamy adres url z unikalnym identyfikatorem w parametrze code

code

Wysyłamy kolejne żądanie tym razem metodą POST mające na celu utworzenie access tokena
http://binaryalchemist.pl/?oauth=token&grant_type=authorization_code&client_id=our_client_id&
client_secret=our_client_secret&code=l7mgwqpet8evt59ovvpj6igftvoxpuqq1pnyvztm
 
gdzie:
http://binaryalchemist.pl/?oauth=token domyślny adres (endpoint) odpoweidzialny za generowanie access tokena
grant_type – typ danych uwierzytelniających wysyłanych do serwera
client_id – numer identyfikacyjny aplikacji dostępny w panelu wordpressa
client_secret – app secret identyfikacyjny aplikacji dostępny w panelu wordpressa
code – unikalny kod wygenerowany w poprzednim kroku
 
Jeśli wszystko przebiegnie sprawnie powinien zostać dla nasz stworzony token dostępu który możemy wykorzystać do autentyfikacji i pozyskania interesujących nasz zasobów
 
Pamiętajmy ze request token jest ważny jedynie 30 sekund.
 
Skoro tą kwestie mamy wyjaśnianą przejdźmy do naszej aplikacji. Na początek zainstalujmy wtyczkę „WP OAuth Server”. Odpowiada ona za zarządzanie autentyfikacją i tokenami na naszym serwerze. W niej utwórzmy nowego klienta. Podajemy jego nazwę oraz adres strony gdzie będziemy przekierowani po udanym logowaniu.
 
oAuth server

oAuth server 2

Po stworzeniu klienta będziemy mieli dostęp do app id oraz secret id zapiszmy je sobie.
 
Zajmijmy się pisaniem aplikacji. W naszej pracy przydatna będzie biblioteka scribe jest to to klient dla oAuth która znacznie ułatwia implementacje tego protokołu a ponadto współpracuje z dużą ilością zewnętrznych api. Niestety domyślnie nie współpracuje z wordpresem ale i z tym sobie poradzimy. Dodajmy więc tą bibliotekę do gradle:
 

compile 'org.scribe:scribe:1.3.7'

Stwórzmy nową klase Provider która będzie rozszerzac DefaultApi20 i zaimplementujmy dwie metody getAccessTokenEndpoint() która okresla adres gdzie będziemy mogli pozyskać access token oraz getAuthorizationUrl() adres z parametrami gdzie będziemy pobierać request token:
 

public class Provider extends DefaultApi20 {
    @Override
    public String getAccessTokenEndpoint() {
        return "http://binaryalchemist.pl?oauth=token";
    }
    @Override
    public String getAuthorizationUrl(OAuthConfig config) {
        StringBuilder authUrl = new StringBuilder();
        authUrl.append("http://binaryalchemist.pl?oauth=authorize");
        authUrl.append("&client_id=").append(OAuthEncoder.encode(config.getApiKey()));
        authUrl.append("&response_type=").append(OAuthEncoder.encode("code"));
        return authUrl.toString();
    }
}

Będziemy się starać stworzyć nowy post na stronie co możliwe jest tylko autoryzowanym użytkownikom. Tak więc w głównej aktywności napiszmy następujący kod
 

public class MainActivity extends AppCompatActivity {
    Verifier verifier;
    OAuthService service;
    Token accessToken;
    final String consumerKey = "TfAr8xOF02Nupmes0g28CMHMV0B5SD"; //api key
    final String consumerSecret = "jMTTd1r2Hr5vWuRaWSrZ04MIQIIsOB"; //api secret
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        service = new ServiceBuilder()
                .provider(Provider.class)
                .apiKey(consumerKey)
                .apiSecret(consumerSecret)
                .build();
        final String authorizationUrl = service.getAuthorizationUrl(EMPTY_TOKEN);
        
        WebView webView;
        webView = (WebView) findViewById(R.id.webView1);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) { //listen for page change and check if its callback page
                String currentUrl = view.getUrl();
                if (currentUrl.contains("http://binaryalchemist.pl/callback?")) {
                    String code = currentUrl.replace("http://binaryalchemist.pl/callback?code=", "");
                    verifier = new Verifier(code);
                  //  new oAuthAsyncTask(getApplicationContext()).execute(); //we will implement it in next step
                }
            }
        });
        webView.loadUrl(authorizationUrl);
    }
}

Przy wykorzystaniu biblioteki scribe tworzymy obiekt service odpowiedzialny za współprace z oAuth. Otwieramy WebView i logujemy się do naszego wordpressa. Jeśli wszystko pójdzie według naszej myśli czyli uda nam się zalogować i zostaniemy przekierowani pod wcześniej zdefiniowany adres pobieramy wygenerowany parametru code z url, mamy już request token. Teraz musimy zdobyć access token, piszemy dalej:
 

private class oAuthAsyncTask extends AsyncTask<String, Void, String> {
        private Context con;
        public oAuthAsyncTask(Context c) {
            this.con = c;
        }
        @Override
        protected void onPreExecute() {
        }
        @Override
        protected void onPostExecute(String result) {
            System.out.print(result);
        }
        @Override
        protected String doInBackground(String... params) {
            oAuth();
            return null;
        }
    }
    public void oAuth() {
        String requestUrl = "http://binaryalchemist.pl/wp-json/wp/v2/posts/";
        OAuthRequest request = new OAuthRequest(Verb.POST, "http://binaryalchemist.pl?oauth=token"); //add url paameters
        request.addBodyParameter(OAuthConstants.CLIENT_ID, consumerKey);
        request.addBodyParameter(OAuthConstants.CLIENT_SECRET, consumerSecret);
        request.addBodyParameter(OAuthConstants.CODE, verifier.getValue());
        request.addBodyParameter("grant_type", "authorization_code");
        Response response = request.send();
        try {
            JSONObject tokenObject = new JSONObject(response.getBody()); //read and create token
            String token = tokenObject.getString("access_token");
            accessToken = new Token(token, "", response.toString());
        } catch (JSONException e) {
            e.printStackTrace();
        }
        String jsonObject ="{\"title\":\"My New Title\",\"status\":\"publish\"}"; //create new post in jason
        System.out.println(jsonObject);
        OAuthRequest secretRequest = new OAuthRequest(Verb.POST, requestUrl);
        secretRequest.addHeader("Content-Type", "application/json");
        secretRequest.addPayload(jsonObject);
        service.signRequest(accessToken, secretRequest); /sign request with access token
        Response secretResponse = secretRequest.send();
        Log.d("OAuthTask", secretResponse.getBody());
    }

 

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="binaryalchemist.wordpressconnector.MainActivity"
    android:background="#428bca">

    <WebView
        android:id="@+id/webView1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></WebView>
</android.support.constraint.ConstraintLayout>

Tworzymy żądanie OAuthRequest request i podajemy wszystkie niezbędne parametry. Żądanie to następnie przesyłamy do serwera otrzymujemy informacje o access tokenie w takiej formie {„access_token”:”ri8qco0lyhdyl2256aczho3i88b9vyzoqpz0zfbb”,”expires_in”:86400, „token_type”:”Bearer”,”scope”:”basic”,”refresh_token”:”ikplek9bt3to4t5nblqsvdf2wojk9pm7m4plfinn”}. Na podstawie tych danych tworzymy access token.
 
Na koniec tworzymy nowe żądanie OAuthRequest secretRequest, dodajemy do niego jsona reprezentującego nowy post (wszystkie parametry obiektu post i innych można znaleźć w dokumentacji wp rest api https://developer.wordpress.org/rest-api/reference/) i podpisujemy wygenerowanym tokenem.
 
Jeśli wszystko przebiegnie sprawnie na naszej stronie powinien się pojawić nowy post:
 
new post

Korzystając z wp wordpress api możemy także aktualizować jak i usuwać nasze dane musmy jedynie zmienić Verb.POST na Verb.PUT czy Verb.DELETE i podać odpowiedni adres url wraz z numerem identyfikującym post.
 
np.
Verb.DELETE
http://binaryalchemist.pl/wp-json/wp/v2/posts/923/
usunie dopiero co stworzony post
 
Wp rest api pozwala nam domyslnie na odczytywanie, tworzenie i modyfikowanie obiektów Posts, Post Revisions, Categories, Tags, Pages, Comments, Taxonomies, Media, Users, Post Types, Post Statuses, Settings. Niemniej wielokrotnie chcielibyśmy mieć dostęp do samemu zdefiniowanych zasobów jak np. dla aplikacji pogodowej dane pogodowe. Na szczęście jest to jak najbardziej możliwe poprzez rozszerzenie endpointów wtyczki wp rest api. Więcej na ten temat można dowiedzieć się z jej dokumentacji pod adresem https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/
 
Cały kod źródłowy projektu stworzonego w Android Studio jest możliwy do ściągnięcia poniżej:
Kod źródłowy

Dodaj komentarz

WordPress Video Lightbox Plugin