API.sistemas- API de Serviços da UFRN

É um canal de comunicação que tem como principal função disponibilizar os dados dos sistemas da UFRN para que seja possível proporcionar que entidades externas criem aplicações(independentes de plataforma) que utilizam tais dados. Os dados são disponibilizados no formato JSON, para ser de fácil consumação pelos usuários e aplicações serem independentes de plataforma e linguagem de programação.

OAuth 2.0

No intuito de possibilitar a utilização dos dados gerados nos seus sistemas, grandes empresas desenvolveram protocolos de autorização proprietários para permitir a criação de aplicações que acessassem suas APIs. A criação desses novos protocolos deve-se ao fato de que no modelo tradicional cliente-servidor o usuário precisa compartilhar suas credenciais com as aplicações para permitir que as mesmas utilizem seus dados, o que gera problemas de segurança, pois aplicações mal intencionadas poderiam utilizar a senha do usuário para acessar indevidamente os dados do mesmo.

Entretanto, a criação de protocolos de autorização proprietários adicionou um custo, em razão de que um desenvolvedor precisaria aprender a implementar múltiplos protocolos para integrar diferentes provedores de APIs em sua(s) aplicação(ões). O Open Authorization Protocol (OAuth) surgiu como uma alternativa a isso, aparecendo como um protocolo de autorização aberto que objetiva padronizar a forma como aplicações acessam uma API para utilizar os dados do usuário.

O OAuth 2.0 é uma evolução do protocolo OAuth e busca simplificar o desenvolvimento dos clientes provendo fluxos de autorização específicos para aplicações web, desktop, mobile e outros dispositivos.

Conceitos importantes
  • Autenticação: processo de verificar a identidade de um usuário, em outras palavras, é o processo que verifica se um usuário é quem ele diz ser.
  • Autenticação federada: aplicações que dependem de outros serviços para verificar a identidade de usuários.
  • Autorização: processo de verificar se o usuário tem o direito de executar uma ação.
  • Autorização delegada: processo no qual o usuário concede permissão para uma pessoa ou aplicação realizar ações em seu nome.
Papéis
  • Resource owner: entidade que tem a capacidade de conceder acesso a recursos protegidos.
  • Resource server: entidade que disponibiliza o recurso protegido.
  • Client: entidade que requisita o acesso ao recurso protegido.
  • Authorization server: entidade que autoriza clientes a acessar recursos protegidos.
Parâmetros importantes
  • client_id: sequência única de caracteres que representa a identificação do cliente.
  • client_secret: sequência de caracteres que representa a “senha” do cliente.
  • access_token: sequência de caracteres que representam credenciais utilizadas para acessar recursos protegidos.
  • refresh_token: sequência de caracteres utilizadas para requisitar um novo access_token.
  • redirect_uri: página para onde o usuário deve ser redirecionado após a realização do login.
  • response_type: Deve assumir os valores: “code” para requisitar authorization code ou “token” para requisitar um access token no fluxo Implicit.
  • grant_type: Deve assumir os valores: 1) “authorization_code” no fluxo Authorization code; 2) “password” no fluxo Resource Owner Password Credentials; 3) “client_credentials” no fluxo Client Credentials; 4) “refresh_token” para requisitar um novo token de acesso.
  • token_type: tipo do token gerado.
  • expires_in: tempo de duração, em segundos, do access_token.
  • scope: define o escopo de acesso.
  • username: login do resource owner.
  • password: senha do resource owner.
  • code: authorization code enviado pelo authorization server.
Fluxos de autorização
  • Authorization code

Neste fluxo, o client redireciona o resource owner para que o mesmo realize autenticação no authorization server e, em seguida, autorize o client a utilizar seus recursos. Após o processo de autorização da aplicação, o authorization server envia um authorization code ao client.

Em posse do código, o client requisita um access token passando o código recebido. O authorization server verifica o código e envia um access_token e um refresh_token ao client. Por fim, o client requisita os dados do resource server passando o access token recebido do authorization server.

  • Implicit

O fluxo de autorização implícito é uma simplificação do authorization code. Nele o access token é retornado ao client logo após o resource owner autorizá-lo a utilizar seus dados. E, em seguida, o client já pode requisitar os dados do recurso desejado no resource server.

Nesse fluxo não há suporte para refresh token e, caso o access token criado para o resource owner expire, o fluxo de autorização precisa ser repetido para requisitar um novo access token.

  • Resource Owner Password Credentials

Neste fluxo o resource owner informa seu username e password ao client, esse último usa essas informações para obter um access token do authorization server. Em posse, desse token o client já pode requisitar dados do resource server.

Esse tipo de fluxo é recomendado somente no caso em que a aplicação é propriedade do desenvolvedor da API, isso se deve em virtude de o resource owner precisar informar suas credenciais ao client.

  • Client Credentials

Neste fluxo a aplicação envia suas credenciais ao authorization server e recebe um access token do mesmo. No fluxo client credentials não há suporte para refresh token, assim, quando o token expira a aplicação deve repetir o fluxo para obter um novo access token.

REST

REST do inglês Representational State Transfer, em português Transferência de Estado Representacional. Trata-se de um modelo arquitetural que possui um conjunto de restrições aplicados a todo o sistema web, como: componentes, conectores e elementos de dados dentro de um sistema de hipermídia distribuído. É dispensável para essa tecnologia a sintaxe de implementação, já que pode ser aplicado em qualquer serviço web. Este que pode ser chamado de RESTful, caso atenda todas as restrições desse padrão de arquitetura. Essas restrições estão divididas em seis partes, são elas:

  • Cliente-Servidor(Client-Server): Aqui traz-se o conceito de independência, onde existe separação entre o cliente e o servidor, o qual permite que servidores e clientes possam ser desenvolvidos independentemente sem que a comunicação entre eles sejam prejudicadas.
  • Sem Estado (Stateless): Como próprio nome já diz, trata-se de uma comunicação sem estado, ou seja, o servidor não guarda nenhum estado de sessão do cliente, de forma que todo dado é obtido com as informações da requisição, trazendo assim mais visibilidade, segurança e escalabilidade.
  • Cacheable: A arquiteura REST permite que utilize-se serviços de armazenamento em cache das respostas dos servidores, porém é preciso trata-las para que os usuários não acessem dados obsoletos ou inapropriados. E um bom serviço de gerenciamento do cache pode trazer bastante eficiência nas comunicações com o servidor, já que muitas delas podem ser aproveitadas devido aos dados armazenados em cache.
  • Sistema em Camadas (Layered System): O sistema em camadas permite que o sistema seja dividido em camadas com restrições, de modo que cada componente da aplicação só possa ter acesso direto com as camadas de sua ligação. Isso com o intuito de melhorar as requisições do usuário, de modo de seja mais filtrada e direcionada da forma mais segura possível.
  • Interface Uniforme (Uniform Interface): Principal característica da arquitetura REST que foi designada para ser eficiente com grande numero de dados, trabalhando com um resultado equivalente apenas para a sua arquitetura. Existindo algumas estrições principais: as identificações de recursos que são identificadas na requisição, como as URI's; mensagens auto-descritivas onde cada mensagem possui toda a informação necessária para ser processada e hipermídia, o qual os clientes fazem transições de estado somente através de ações que são dinamicamente identificadas, por exemplo, hiperlinks em hipertextos.

JSON

Trata-se de uma modelagem de armazenamento de transmissão de arquivos de texto, oriundo do JavaScript, por isso significa JavaScript Obect Notation, mas que pode ser utilizado por diversas linguagens. É bastante simples, porém muito eficiente e poderoso, pois devido a sua forma compacta a sua transmissão e o 'parsing' da informação ocorrem de forma ágil. Algumas empresas já utilizam esse mecanismo, como a Google e a Yahoo, o que demonstra ser bastante eficientes, pois os dois trabalham com buscas e essa se trata de uma das melhores formas de se veicular a informação.

Cada informação é definido por uma tupla, sendo um rótulo e o seu valor, podendo ser este valor campos string, inteiros, reais, booleanos, arranjos e objetos.

Ex.:

{
  “nome” : “Vinícius”,
  “idade” : 21,
  “altura” : 1.7,
  “estudante” : true,
  “interesses” : [“Development”, “Mobile”, “Web”, “Games”],
  “parentes” : [ 
                {
                  “nome” : “José”,
                  “parentesco” : “pai”
                },
                {
                  “nome” : “Maria”,
                  “parentesco” : “mãe”
                }
               ]
}

Atualmente, a API de serviços da SINFO implementa e disponibiliza três dos quatro fluxos de autorização do OAuth 2.0 e o fluxo de refresh_token, os quais são mostrado abaixo.

Client Credentials

Esse fluxo foi implementado para ser usado por aplicações que queiram acessar os dados públicos dos sistemas da SINFO, com por exemplo: eventos, notícias, telefones, entre outros. Desse modo, aplicações com esse intuito, devem seguir os seguintes passos:

  1. A aplicação faz uma requisição POST ao authorization server através da URL http://apitestes.info.ufrn.br/authz-server/oauth/token, passando o client_id, client_secret e grant_type como QueryParam. Ex: POST http://apitestes.info.ufrn.br/authz-server/oauth/token?client_id=AppId&client_secret=AppSecret&grant_type=client_credentials
  2. O authorization server retorna à aplicação um Json contendo o access_token, token_type, expires_in e scope. Ex: { “access_token”: “000000000000000000000000000000000000”, “token_type”: “bearer”, “expires_in”: 7431095, “scope”: “read” }
  3. Em posse dessas informações, a aplicação já pode acessar os dados disponibilizados pela API passando o token através do parâmetro Authorization no HEADER da requisição desejada. Ex: GET http://apitestes.info.ufrn.br/telefone-services/services/consulta/telefone/ccet Authorization: Bearer 000000000000000000000000000000000000
  4. Os dados da API são retornados para a aplicação. Ex.:
 
[
  {
    "idTelefone": 0,
    "localizacao": "string",
    "setor": "string",
    "descricao": "string",
    "numero": "string",
    "ramais": [
      {
        "numero": "string",
        "descricao": "string"
      }
    ]
  }
]

Authorization code

Esse fluxo deve ser utilizado por aplicações que queiram acessar as informações privadas às contas de usuários dos sistemas SINFO, como por exemplo: turmas de um usuário, frequências de um discente, histórico de utilização de um usuário no restaurante universitário, etc. Assim, aplicações com esse propósito precisam seguir os seguintes passos:

  1. O usuário inicia a interação com a aplicação.
  2. A aplicação faz uma requisição GET ao authorization server através da URL http://apitestes.info.ufrn.br/authz-server/oauth/authorize, passando os parâmetros client_id, response_type e redirect_uri como QueryParam. Ex: GET http://apitestes.info.ufrn.br/authz-server/oauth/authorize?client_id=AppId&response_type=code&redirect_uri=http://enderecoapp.com.br/pagina
  3. O usuário é redirecionado para o authorization server, o mesmo deve informar suas credenciais (username, password) na página de autenticação exibida e, em seguida, informar se autoriza a aplicação a utilizar seus dados.
  4. O authorization server retorna o código de autorização à aplicação.
  5. Em posse desse código, a aplicação pode usá-lo para obter um access_token para o usuário. Desse modo, ela realiza uma nova requisição, neste caso um POST, ao authorization_server através da URL http://apitestes.info.ufrn.br/authz-server/oauth/token, passando os parâmetros client_id, client_secret, redirect_uri, grant_type e code como QueryParam. Ex: POST http://apitestes.info.ufrn.br/authz-server/oauth/token?client_id=AppId&client_secret=AppSecret&redirect_uri=http://enderecoapp.com.br/pagina&grant_type=authorization_code&code=code
  6. O authorization server retorna à aplicação um Json contendo o access_token, token_type, refresh_token, expires_in e scope. Ex: { “access_token”: “000000000000000000000000000000000000”, “token_type”: “bearer”, “refresh_token”: “ffffffffffffffffffffffffffffffffffff” , “expires_in”: 7431095, “scope”: “read” }
  7. - Em posse dessas informações, a aplicação já pode acessar os dados disponibilizados pela API passando o token através do parâmetro Authorization no header da requisição desejada. Ex: GET http://apitestes.info.ufrn.br/ensino-services/services/consulta/listavinculos/usuario Authorization: Bearer 000000000000000000000000000000000000
  8. Os dados da API são retornados para a aplicação. Ex.:
{
  "docentes": [
    {
      "contemTurmas": false,
      "idUsuario": 0,
      "numero": 0,
      "id": 0,
      "ordem": 0,
      "ativo": false,
      "siape": "string",
      "lotacao": "string"
    }
  ],
  "discentes": [
    {
      "contemTurmas": false,
      "idUsuario": 0,
      "numero": 0,
      "id": 0,
      "ordem": 0,
      "ativo": false,
      "matricula": "string",
      "curso": "string",
      "status": 0,
      "anoIngresso": 0,
      "periodoIngresso": 0
    }
  ],
  "docentesExterno": [
    {
      "contemTurmas": false,
      "idUsuario": 0,
      "numero": 0,
      "id": 0,
      "ordem": 0,
      "ativo": false,
      "matricula": "string",
      "instituicao": "string"
    }
  ],
  "outros": [
    {
      "contemTurmas": false,
      "idUsuario": 0,
      "numero": 0,
      "id": 0,
      "ordem": 0,
      "ativo": false
    }
  ],
  "id": 0,
  "all": [
    {
      "contemTurmas": false,
      "idUsuario": 0,
      "numero": 0,
      "id": 0,
      "ordem": 0,
      "ativo": false
    }
  ]
}

Resource Owner Password Credentials

Esse tipo de fluxo deve ser utilizado por aplicações que sejam de propriedade da própria SINFO e que necessitem dos dados privados a contas de usuários, em virtude de o OAuth 2.0 definir que no Resource Owner Password Credentials o usuário informa suas credenciais diretamente à aplicação. Logo, aplicações com essa característica precisam seguir os seguintes passos:

  1. O usuário informa suas credenciais de acessso à aplicação (username e password).
  2. A aplicação faz uma requisição POST ao authorization server através da URL http://apitestes.info.ufrn.br/authz-server/oauth/token, passando os parâmetros client_id, client_secret, grant_type, username e password. Ex: POST http://apitestes.info.ufrn.br/authz-server/oauth/token?client_id=AppId&client_secret=AppSecret&grant_type=password&username=loginUsuario&password=senhaUsuario
  3. O authorization server retorna à aplicação um Json contendo o access_token, token_type, refresh_token, expires_in e scope. Ex: { “access_token”: “000000000000000000000000000000000000”, “token_type”: “bearer”, “refresh_token”: “ffffffffffffffffffffffffffffffffffff” , “expires_in”: 7431095, “scope”: “read” }
  4. Em posse dessas informações, a aplicação já pode acessar os dados disponibilizados pela API passando o novo token através do parâmetro Authorization no header da requisição desejada. Ex: GET http://apitestes.info.ufrn.br/ensino-services/services/consulta/listavinculos/usuario Authorization: Bearer 000000000000000000000000000000000000
  5. Os dados da API são retornados para a aplicação. Ex.:
{
  "docentes": [
    {
      "contemTurmas": false,
      "idUsuario": 0,
      "numero": 0,
      "id": 0,
      "ordem": 0,
      "ativo": false,
      "siape": "string",
      "lotacao": "string"
    }
  ],
  "discentes": [
    {
      "contemTurmas": false,
      "idUsuario": 0,
      "numero": 0,
      "id": 0,
      "ordem": 0,
      "ativo": false,
      "matricula": "string",
      "curso": "string",
      "status": 0,
      "anoIngresso": 0,
      "periodoIngresso": 0
    }
  ],
  "docentesExterno": [
    {
      "contemTurmas": false,
      "idUsuario": 0,
      "numero": 0,
      "id": 0,
      "ordem": 0,
      "ativo": false,
      "matricula": "string",
      "instituicao": "string"
    }
  ],
  "outros": [
    {
      "contemTurmas": false,
      "idUsuario": 0,
      "numero": 0,
      "id": 0,
      "ordem": 0,
      "ativo": false
    }
  ],
  "id": 0,
  "all": [
    {
      "contemTurmas": false,
      "idUsuario": 0,
      "numero": 0,
      "id": 0,
      "ordem": 0,
      "ativo": false
    }
  ]
}

Refresh Token

O fluxo de refresh token deve ser utilizado quando o access_token de um usuário expira em aplicações que utilizem os fluxos de autorização authorization code e resource owner password credentials disponibilizados pela API de serviços da SINFO. Assim, as mesmas devem seguir os seguintes passos:

  1. A aplicação deve fazer uma requisição POST ao servidor de autorização através da URL http://apitestes.info.ufrn.br/authz-server/oauth/token, passando os parâmetros client_id, client_secret, grant_type e refresh_token como QueryParam. Ex: POST http://apitestes.info.ufrn.br/authz-server/oauth/token?client_id=AppId&client_secret=AppSecret&grant_type=refresh_token&refresh_token=ffffffffffffffffffffffffffffffffffff
  2. O authorization server retorna à aplicação um Json contendo o access_token, token_type, refresh_token, expires_in e scope. Ex: { “access_token”: “111111111111111111111111111111111111”, “token_type”: “bearer”, “refresh_token”: “eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee” , “expires_in”: 7431095, “scope”: “read” }
  3. Em posse dessas informações, a aplicação já pode acessar os dados disponibilizados pela API passando o token através do parâmetro Authorization no header da requisição desejada. Ex: GET http://apitestes.info.ufrn.br/telefone-services/services/consulta/telefone/ccet Authorization: Bearer 111111111111111111111111111111111111
  4. Os dados da API são retornados para a aplicação. Ex.:
[
  {
    "idTelefone": 0,
    "localizacao": "string",
    "setor": "string",
    "descricao": "string",
    "numero": "string",
    "ramais": [
      {
        "numero": "string",
        "descricao": "string"
      }
    ]
  }
]

Android utilizando Authorization code

Neste exemplo mostra-se a utilização do fluxo de autorização authorization code em uma aplicação Android.

Desse modo, a primeira tela da aplicação contém as funcionalidades básicas da mesma (Logar, Obter dados e Sair). Como mostrado nos dois trechos de código que formam a mesma:

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    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" android:fitsSystemWindows="true"
    tools:context=".MainActivity">

    <android.support.design.widget.AppBarLayout android:layout_height="wrap_content"
        android:layout_width="match_parent" android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar android:id="@+id/toolbar"
            android:layout_width="match_parent" android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main" />

</android.support.design.widget.CoordinatorLayout>
  • content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:showIn="@layout/activity_main" tools:context=".MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/logintxt"
        android:id="@+id/logarBtn"
        android:layout_marginBottom="54dp"
        android:onClick="logar"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginLeft="46dp"
        android:layout_marginStart="46dp"
        android:layout_marginRight="46dp"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/obter_dados_btn"
        android:id="@+id/dadosBtn"
        android:onClick="obterDados"
        android:layout_alignTop="@+id/logarBtn"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_marginRight="52dp"
        android:layout_marginEnd="52dp"
        android:layout_marginLeft="46dp"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/sair_btn"
        android:id="@+id/sairBtn"
        android:layout_above="@+id/dadosBtn"
        android:layout_alignRight="@+id/dadosBtn"
        android:layout_alignEnd="@+id/dadosBtn"
        android:onClick="sair"/>
</RelativeLayout>
  • Tela inicial da aplicação

Essa tela é controlada pela classe MainActivity que implementa as devidas ações relacionadas aos botões de logar, obter dados e sair, conforme exibido no trecho de código abaixo:

  • MainActivity.java
//imports omitidos

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }

    public void logar(View v) {
        Intent i = new Intent(this, ResultActivity.class);
        OAuthTokenRequest.getInstance().getTokenCredential(this,"http://apitestes.info.ufrn.br/authz-server","CLIENT-ID", "CLIENT-SECRET", i);
    }

    public void obterDados(View v){
        Intent intent = new Intent(this, ResultActivity.class);
        startActivity(intent);
    }
    
    //métodos omitidos

    public void sair(View view) {
        OAuthTokenRequest.getInstance().logout(this, "http://apitestes.info.ufrn.br/sso-server/logout");
    }
}

Como se pode ver no código acima as requisições a API são realizadas por meio de um cliente OAuth implementado na classe OAuthTokenRequest, exibida abaixo. A mesma se encarrega de buscar as credenciais de acesso para o usuário (getTokenCredential(…)) e também de realizar as requisições aos recursos que o mesmo quer utilizar (resourceRequest(…)). Além disso, para que a mesma funcione é preciso colocar dependências para as bibliotecas Android OAuth Client Library (android-oauth-client) e para o Android Volley

  • OAuthTokenRequest.java
//imports omitidos

/**
 * Created by Mario on 04/11/2015.
 */
public class OAuthTokenRequest {

    private Credential credential;

    private static OAuthTokenRequest oAuthTokenRequest;

    private String clientId, clientSecret;

    private OAuthManager oauth;

    public static OAuthTokenRequest getInstance(){
        if(oAuthTokenRequest == null)
            oAuthTokenRequest = new OAuthTokenRequest();

        return oAuthTokenRequest;
    }

    private OAuthTokenRequest() {
    }

    public Credential getTokenCredential(final Activity activity,String oauthServerURL, String clientId,String clientSecret, final Intent i){

        this.clientId = clientId;
        this.clientSecret = clientSecret;

        AuthorizationFlow.Builder builder = new AuthorizationFlow.Builder(
                BearerToken.authorizationHeaderAccessMethod(),
                AndroidHttp.newCompatibleTransport(),
                new JacksonFactory(),
                new GenericUrl(oauthServerURL +"/oauth/token"),
                new ClientParametersAuthentication(clientId, clientSecret),
                clientId,
                oauthServerURL +"/oauth/authorize");

        AuthorizationFlow flow = builder.build();

        AuthorizationUIController controller = new DialogFragmentController(activity.getFragmentManager()) {

            @Override
            public String getRedirectUri() throws IOException {
                //return "http://android.local/";
                return "http://localhost/Callback";
            }

            @Override
            public boolean isJavascriptEnabledForWebView() {
                return true;
            }

        };

        oauth = new OAuthManager(flow, controller);

        try {
            OAuthManager.OAuthCallback<Credential> callback = new OAuthManager.OAuthCallback<Credential>() {
                @Override public void run(OAuthManager.OAuthFuture<Credential> future) {
                    try {
                        credential = future.getResult();
                        activity.startActivity(i);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    // make API queries with credential.getAccessToken()
                }
            };

            oauth.authorizeExplicitly("userId", callback, null);
            if(credential != null){
                Log.d("TOKEN", credential.getAccessToken());
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return credential;
    }

    public void resourceRequest(Context context,int method,String url, Response.Listener<String> listener, Response.ErrorListener errorListener){
        RequestQueue queue = Volley.newRequestQueue(context);

        // Request a string response from the provided URL.
        StringRequest stringRequest = new StringRequest(Request.Method.GET, url,listener, errorListener) {
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                Map<String, String> headers = new HashMap<String, String>();
                String auth = "Bearer "+ credential.getAccessToken();
                headers.put("Authorization", auth);
                return headers;
            }
        };

        // Add the request to the RequestQueue.
        queue.add(stringRequest);
    }

    public void logout(Context context, String url) {
        WebView w= new WebView(context);
        w.loadUrl(url);
        credential = null;
    }
}

Ao clicar no botão de logar, a classe MainActivity chama o método getTokenCredential(…) passando entre os parâmetros necessários cliente_id e client_secret da aplicação. Desse modo, é mostrada a tela abaixo, na qual o usuário informa suas credenciais de acesso.

  • Tela de login da Sinfo

Caso o usuário e senha informados sejam válidos, é mostrado ao usuário a tela perguntando se o mesmo autoriza a aplicação a utilizar seus dados, como exibido abaixo.

Após o usuário autorizar a aplicação, as credenciais de acesso são retornadas para a mesma e a partir daí ela já pode consumir dados dos recursos disponibilizados pela API da Sinfo.

Desse modo, caso o usuário clique no botão Obter Dados, a aplicação é redirecionada para a ResultActivity, exibida abaixo.

  • ResultActivity.java
//imports omitidos

public class ResultActivity extends AppCompatActivity {

    // temporary string to show the parsed response
    private String jsonResponse;

    // Progress dialog
    private ProgressDialog pDialog;

    private TextView texto;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_result);

        pDialog = new ProgressDialog(this);
        pDialog.setMessage("Aguarde ...");
        pDialog.setCancelable(false);

        reqJson();

        texto = (TextView) findViewById(R.id.textoJson);

    }

    private void reqJson(){

        String urlJsonObj = "http://apitestes.info.ufrn.br/usuario-services/services/usuario/info";
        OAuthTokenRequest.getInstance().resourceRequest(this, Request.Method.GET, urlJsonObj, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                try {
                    JSONObject jsonObject = new JSONObject(response);

                    String nome = jsonObject.getString("nome");
                    String login = jsonObject.getString("login");

                    jsonResponse = "";
                    jsonResponse += "Name: " + nome + "\n\n";
                    jsonResponse += "Login: " + login + "\n\n";

                    texto.setText(jsonResponse);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }, new Response.ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError error) {
                VolleyLog.d("SAIDA", "Error: " + error.getMessage());
                Toast.makeText(getApplicationContext(),
                        error.getMessage(), Toast.LENGTH_SHORT).show();
                // hide the progress dialog
            }
        });
    }

}

A ResultActivity utiliza o método resourceRequest(…) da classe OAuthTokenRequest para buscar os dados desejados da API. Após a consulta, a mesma exibe os dados buscados na tela de informações do usuário.

Android utilizando Client Credentials

Neste exemplo mostra-se a utilização do fluxo de autorização client credentials em uma aplicação Android.

A aplicação possui a funcionalidade básica de buscar uma unidade/setor através do nome, parte do nome(setor ou unidade) ou sigla, e retornar o telefone desses locais. Além dessa funcionalidade básica, o usuário pode adicionar o número a agenda telefônica ou fazer uma ligação. Os três códigos a seguir mostram como está estruturado a interface do aplicativo:

  • activity_main.xml
<?xml version="1.0" encoding="UTF-8"?>
<android.support.design.widget.CoordinatorLayout 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"
    android:fitsSystemWindows="true"
    tools:context="br.ufrn.telefoneme.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay"/>

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main" />

</android.support.design.widget.CoordinatorLayout>
  • content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="br.ufrn.telefoneme.MainActivity"
    tools:showIn="@layout/activity_main"
    android:orientation="horizontal">

    <RelativeLayout
        android:id="@+id/relative_line"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:padding="2dp"
        android:background="@drawable/border">

        <ImageButton
            android:id="@+id/search_button_tel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="@dimen/activity_horizontal_margin"
            android:layout_marginRight="@dimen/activity_horizontal_margin"
            android:layout_alignParentRight="true"
            android:layout_marginTop="5dp"
            android:src="@drawable/ic_magnify"
            android:background="@android:color/transparent"/>

        <EditText
            android:id="@+id/search_edit_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="@dimen/activity_horizontal_margin"
            android:layout_toLeftOf="@id/search_button_tel"
            android:paddingTop="8dp"
            android:paddingBottom="10dp"
            android:hint="Unidade, setor ou sigla"
            android:background="@android:color/white"/>

    </RelativeLayout>

    <!-- A RecyclerView with some commonly used attributes -->
    <android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/relative_line"/>

</RelativeLayout>
  • item.xml
<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/activity_vertical_margin"
        android:layout_marginLeft="@dimen/activity_horizontal_margin"
        android:phoneNumber="true"
        android:text="32087795"
        android:textSize="35sp"
        android:textColor="@android:color/black"/>

    <TextView
        android:id="@+id/descricao"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/activity_horizontal_margin"
        android:layout_below="@id/number"
        android:text="Escritório"
        android:textColor="@android:color/black"/>

    <TextView
        android:id="@+id/unidade"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/activity_horizontal_margin"
        android:layout_below="@id/descricao"
        android:text="INSTITUTO METROPOLE DIGITAL"
        android:textColor="@android:color/black"/>

    <TextView
        android:id="@+id/setor"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/activity_horizontal_margin"
        android:layout_below="@id/unidade"
        android:text="UNIVERSIDADE FEDERAL DO RIO GRANDE DO NORTE"
        android:textColor="@android:color/black"/>

    <ImageButton
        android:id="@+id/call"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="18dp"
        android:layout_marginRight="@dimen/activity_horizontal_margin"

        android:src="@drawable/ic_call_white_24dp"
        android:layout_alignParentRight="true"
        android:background="@drawable/oval"/>

    <ImageButton
        android:id="@+id/add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="18dp"
        android:layout_marginRight="@dimen/activity_horizontal_margin"
        android:paddingRight="3dp"
        android:paddingBottom="1dp"
        android:src="@drawable/ic_person_add_white_24dp"
        android:layout_toLeftOf="@id/call"
        android:background="@drawable/oval"/>

    <View
        android:id="@+id/separator"
        android:layout_width="match_parent"
        android:layout_height="2dp"
        android:layout_below="@id/setor"
        android:layout_marginTop="@dimen/activity_vertical_margin"
        android:background="@color/black_overlay"/>

</RelativeLayout>

A aplicação possui três estados, o inicial, realizando a pesquisa e resultado final que são mostrados abaixo nas imagens, respectivamente.

A classe que controla todo o aplicativo é a seguinte:

/**
 * Created by Vinicius on 09/12/2015.
 */
// imports omitidos 

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private RecyclerView.Adapter adapter;
    private RecyclerView.LayoutManager layoutManager;
    private List<TelefoneDTO> telefones;
    private ProgressDialog progressDialog;
    private EditText editText;
    private ImageButton searchButton;

    public MainActivity(){
        telefones = new ArrayList();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);

        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
        recyclerView.setHasFixedSize(false);

        layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new RecyclerAdapter(telefones, MainActivity.this);
        recyclerView.setAdapter(adapter);

        editText = (EditText) findViewById(R.id.search_edit_text);
        searchButton = (ImageButton) findViewById(R.id.search_button_tel);

        searchButton.setOnClickListener(new ImageButton.OnClickListener(){
            @Override
            public void onClick(View v) {

                //Create a new progress dialog
                progressDialog = ProgressDialog.show(MainActivity.this, null, "Carregando dados da aplicação...", true);

                new Thread(new Runnable() {
                    @Override
                    public void run()
                    {
                        //Initialize a LoadViewTask object and call the execute() method
                        String text = OltuJavaClient.getResource(editText.getText().toString());
                        telefones = new ArrayList<TelefoneDTO>();
                        if(!text.equalsIgnoreCase("")){
                            try {
                                JSONArray array = new JSONArray(text);
                                for(int i = 0; i < array.length(); ++i){
                                    JSONObject jsonList = array.getJSONObject(i);

                                    telefones.add(new TelefoneDTO(jsonList.getString("numero"),
                                            jsonList.getString("descricao"),
                                            jsonList.getString("localizacao"),
                                            jsonList.getString("setor")));
                                }


                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                        }

                        runOnUiThread(new Runnable() {
                            @Override
                            public void run()
                            {
                                progressDialog.dismiss();
                                adapter = new RecyclerAdapter(telefones, MainActivity.this);
                                recyclerView.setAdapter(adapter);
                            }
                        });
                    }
                }).start();

            }
        });
    }
}

Como se pode ver no código acima as requisições a API são realizadas por meio de um cliente OAuth implementado na classe OltuJavaClient, exibida abaixo. Essa se encarrega de buscar as credenciais da aplicação e os recursos do serviço (getResource(…)). Além disso, para que a mesma funcione é preciso colocar dependências para as bibliotecas Apache Oltu OAuth 2.0 Client e para o Android Volley.

  • OltuJavaClient
/**
 * Created by Vinicius on 09/12/2015.
 */

//Imports omitidos
public class OltuJavaClient {

    public static final String TOKEN_REQUEST_URL = "http://apitestes.info.ufrn.br/authz-server/oauth/token";

    //CLIENT_ID E CLIENT_SECRET omitidos

    public static final String RESOURCE_URL_TPL = "http://apitestes.info.ufrn.br/telefone-services/services/consulta/telefone/";

    public static String getResource(String keyWord){
        String resultJson = "";
        try {
            OAuthClient client = new OAuthClient(new URLConnectionClient());

            OAuthClientRequest request =
                    OAuthClientRequest.tokenLocation(TOKEN_REQUEST_URL)
                            .setGrantType(GrantType.CLIENT_CREDENTIALS)
                            .setClientId(CLIENT_ID)
                            .setClientSecret(CLIENT_SECRET)
                            .buildQueryMessage();

            String token =
                    client.accessToken(request, OAuthJSONAccessTokenResponse.class)
                            .getAccessToken();

            HttpURLConnection resource_cxn =
                    (HttpURLConnection)(new URL(RESOURCE_URL_TPL + keyWord).openConnection());
            resource_cxn.addRequestProperty("Authorization", "Bearer " + token);

            InputStream resource = resource_cxn.getInputStream();

            BufferedReader r = new BufferedReader(new InputStreamReader(resource, "UTF-8"));
            String line = null;

            while ((line = r.readLine()) != null) {
                resultJson += line;
            }

        } catch (Exception exn) {
            exn.printStackTrace();
        }

        return resultJson;
    }
}

Ao clicar no ícone de pesquisa o método getResource() OltuJavaClient da classe será chamado com o texto contido na caixa de pesquisa. O resultado dessa requisição é mapeado em na classe TelefoneDTO, que é alocado em uma lista, então utilizando-se de um recycler view os números de telefones são mostrados na tela.

/**
 * Created by Vinicius on 09/12/2015.
 */
public class TelefoneDTO {
    private String localizacao;
    private String setor;
    private String descricao;
    private String numero;

    public TelefoneDTO(String numero, String descricao, String localizacao, String setor){
        this.numero = numero;
        this.descricao = descricao;
        this.localizacao = localizacao;
        this.setor = setor;
    }

    public String getLocalizacao() {
        return localizacao;
    }

    public void setLocalizacao(String localizacao) {
        this.localizacao = localizacao;
    }

    public String getSetor() {
        return setor;
    }

    public void setSetor(String setor) {
        this.setor = setor;
    }

    public String getDescricao() {
        return descricao;
    }

    public void setDescricao(String descricao) {
        this.descricao = descricao;
    }

    public String getNumero() {
        return numero;
    }

    public void setNumero(String numero) {
        this.numero = numero;
    }
}

Java utilizando Client Credentials

Exemplo java aqui.

Java utilizando Resource Owner Password Credentials

Exemplo java aqui.

Java Script

Exemplo java script aqui.

Python

para a utilização com python utilizamos a biblioteca 'requests' para fazer as requisições a api, sendo assim é necessário a instalação da biblioteca a partir do 'pip', com o python instalado na maquina e devidamente configurado, basta rodar o comando:

pip install requests

Python utilizando Client Credentials

# -*- coding: utf-8 -*-
import requests
import json

client_id = 'xxxxx'  #aqui sua client_id
client_secret = 'xxxxx' #aqui sua client_secret
URL_BASE = 'http://apitestes.info.ufrn.br/'

#injetando parametros na url
url_token = URL_BASE + 'authz-server/oauth/token?client_id={0}&client_secret={1}&grant_type=client_credentials'.format(client_id, client_secret)

#efetuando uma requisicao a api passando a url como parametro
requisicao_token = requests.post(url_token)

#convertendo a resposta json em um objeto python
resposta = json.loads(requisicao_token.content)

#imprimindo resultado da requisição
print 'RESPOSTA: \n'+ str(resposta) #é possivel acessar campos em especifico com 'resposta['campo_que_deseja']'

#salvamos o token em uma variavel pra usar em um exemplo de chamada a api
token = resposta['access_token']

#montamos a url de projetos injetando o token como parametro
URL_Projetos = URL_BASE+ 'projeto-pesquisa-services/services/projetopesquisa?limit=1&access_token={0}'.format(token)

#agora como exemplo fazemos uma requisicao aos projetos de pesquisa com o token obtido
requisicao_projetos = requests.get(URL_Projetos)

#convertemos a resposta para json
projetos = json.loads(requisicao_projetos.content)

#imprimimos o resultado da api de projetos, e possivel acessar campos em especifico com 'resposta['campo_que_deseja']'
print 'PROJETOS: \n'+ str(projetos)

Python Resource Owner Password Credentials

import requests
import json

client_secret = 'xxxxx' #aqui sua client_secret
username = 'xxxxx' #aqui seu username
password = 'xxxxx' #aqui seu password

#url da api
URL_BASE = 'http://apitestes.info.ufrn.br/'

#montamos a url de login injetando os parametros definidos no inicio do codigo
url_token = URL_BASE + 'authz-server/oauth/token?client_id={0}&username={1}&password={2}&grant_type=password'.format(client_id, username, password)

# efetuamos uma requisicao a api passando a url_projetos
requisicao_token = requests.post(url_token)

# convertemos o resultado em json para um acesso mais facil aos dados
resposta = json.loads(requisicao_token.content)

# imprimimos o resultado da api, e possivel acessar campos em especifico com 'resposta['campo_que_deseja']'
print resposta
#salvamos o token em uma variavel pra usar em um exemplo de chamada a api
token = resposta['access_token']

#montamos a url de projetos injetando o token como parametro
URL_Projetos = URL_BASE+ 'projeto-pesquisa-services/services/projetopesquisa?limit=1&access_token={0}'.format(token)

#agora como exemplo fazemos uma requisicao aos projetos de pesquisa com o token obtido
requisicao_projetos = requests.get(URL_Projetos)

#convertemos a resposta para json
projetos = json.loads(requisicao_projetos.content)

#imprimimos o resultado da api de projetos, e possivel acessar campos em especifico com 'resposta['campo_que_deseja']'
print projetos

Atualmente, o cadastro da aplicação está sendo realizado depois de requerimento de utilização da API via e-mail enviado ao coordenador do projeto. Logo, para solicitar credenciais de utilização da API, o(a) interessado(a) deve submeter e-mail para api@info.ufrn.br informando quais os dados que deseja utilizar e solicitando credenciais de acesso para a respectiva aplicação.

Nome Endereço swagger TESTE Endereço swagger PRODUÇÃO Descrição
Bens Services bens-services Permite consultar informações bens da UFRN.
Biblioteca Services biblioteca-services biblioteca-services Permite consultar informações sobre materiais que se encontram no acervo das bibliotecas da universidade.
Bolsas Administrativo Services bolsas-administrativo-services bolsas-administrativo-services Permite recuperar informações sobre bolsas no sistema administrativo.
Bolsas Services bolsas-services bolsas-services Permite recuperar informações sobre bolsas do RU, extensão, pesquisa, monitoria e ações associadas.
Comum Services comum-services comum-services Permite a consulta dos dados comuns aos três sistemas da SINFO (SIGAA, SIGRH e SIPAC).
Concursos Services concursos-services concursos-services Permite adquirir informações sobre concursos da universidade.
Contratos Services contratos-services contratos-services Permite adquirir informações sobre os contratos de obras licitadas na universidade registrados no SIPAC.
Curso Services curso-services curso-services Permite consultar informações dos cursos da UFRN.
Docente Services docente-services Permite consultar informações dos perfis de docentes da UFRN.
Ensino Services ensino-services ensino-services Permite recuperar as informações sobre as turmas registradas no SIGAA, como notícias, tarefas, frequências, notificações, participantes. E permite recuperar informações sobre o aluno, como vínculos, notas, entre outras informações.
Fornecedores Services fornecedores-services Permite consultar informações de fornecedores da UFRN.
GRU Services gru-services gru-services Permite a consulta de todas as GRUs (Guia de Recolhimento da União) que foram geradas pelo SIPAC, SIGAA e SIGRH, e também a recuperação de uma GRU especifica, identificada pelo seu número.
Materiais Services materiais-services materiais-services Permite adquirir informações sobre materiais adquiridos pela universidade e registrados no SIPAC.
Pessoa Services pessoa-services Permite retornar informações pessoais dos usuários.
Processos Services processos-services processos-services Permite adquirir informações sobre processos registrados na universidade.
Restaurante Services restaurante-services restaurante-services Permite recuperar informações sobre o cartão do usuário e sobre os históricos diários de utilização do restaurante universitário.
Servidor Services servidor-services servidor-services Permite adquirir informações sobre servidores da universidade.
Stricto Sensu Services stricto-sensu-services stricto-sensu-services Permite consultar informações (docentes, discentes, projetos) relacionadas a pós do tipo stricto sensu.
Telefone Services telefone-services telefone-services Permite consultar informações dos telefones das unidades da UFRN.
Unidades Services unidade-services unidades-services Permitir adquirir informações sobre as unidades (centros e departamentos) da universidade.
Usuário Services usuario-services usuario-services Permite consultar informações sobre o usuários dos sistemas.
  • desenvolvimento/especificacoes/api_servicos.txt
  • Última modificação: 2017/04/03 18:10
  • (edição externa)