Autenticación con Visual Studio Online desde Java usando OAuth

El protocolo OAuth nos proporciona una manera muy fiable de autenticación, así como la posibilidad de interconectar sistemas sin tener que ceder nuestras credenciales a un tercero.

Visual Studio Online es uno de los proveedores que lo soporta, aunque con ciertas peculiaridades. En este artículo veremos cómo podemos utilizar OAuth con Java para conectarnos y autenticarnos, así las peculiaridades de VSO.

Cómo funciona OAuth2.0

Oauth se basa en dos conjuntos de clave, conocidos como key y secret, que el cliente (Aplicación A) proporciona al sistema al que se quiere conectar (Aplicación B). Veamos un ejemplo del ciclo de vida de una petición OAuth:

  1. El usuario desde Aplicación A solicita autenticar con Aplicación B
  2. Aplicación A redirige a la página de Aplicación B con la Api_Key, la url de retorno y otros parámetros como parte de la redirección.
  3. El usuario introduce sus credenciales y autoriza (o no) la cesión de datos desde la Aplicación B a la Aplicación A.
  4. La Aplicación B redirige de vuelta a la Aplicación A con la url de retorno especificada anteriormente con un código de autenticación.
  5. De lado de servidor, la Aplicación A le envía a la Aplicación B el código de autenticación, el secret, y otros parámetros.
  6. La Aplicación B devuelve un Token a la Aplicación A que identifica al usuario y que se puede empezar a utilizar para realizar peticiones.

Cliente Oauth

Vamos a crear un ejemplo de Aplicación A, es decir, un cliente OAuth. Para ello utilizaremos scribe, un componente OAuth para Java muy sencillo y muy fácil de extender para adaptar a nuestras necesidades:

OAuthService service = new ServiceBuilder()
                                  .provider(LinkedInApi.class)
                                  .apiKey(YOUR_API_KEY)
                                  .apiSecret(YOUR_API_SECRET)
                                  .build();

A partir de ese código podemos generar la URL de redirección o capturar el Token. Como Visual Studio Online no forma parte de los proveedores por defecto, crearemos nuestro propio proveedor:

@Override
public class VSOnlineApi extends DefaultApi20 {

    @Override
    public String getAccessTokenEndpoint() {
        return "https://app.vssps.visualstudio.com/oauth2/token";
    }

    @Override
    public String getAuthorizationUrl(OAuthConfig oac) {
        return String.format("https://app.vssps.visualstudio.com/oauth2/authorize?mkt=es&client_id=%s&response_type=Assertion&state=sample&scope=vso.profile&redirect_uri=%s", 
                oac.getApiKey(), oac.getCallback());
    }

    @Override
    public Verb getAccessTokenVerb() {
        return Verb.POST;
    }

    
    @Override
    public OAuthService createService(OAuthConfig config) {
        return new VSOOauthService(this, config);
    }

    @Override
    public AccessTokenExtractor getAccessTokenExtractor() {
        return new VSOTokenExtractor();
    }
}

Este proveedor consta de dos urls, la de token y la de autorización, el verbo que utilizaremos para solicitar el access token, y dos componentes, un servicio que crearemos a continuación y el extractor para procesar el código resultado de la autenticación. Con este proveedor podemos generar la URL para realizar la redirección:


 OAuthService service = new ServiceBuilder()
                           .provider(VSOnlineApi.class)
                           .apiKey("KEY")
                           .apiSecret("SECRET)
                           .callback("https://rlbisbe.dev:4567/callback")
                           .signatureType(SignatureType.QueryString)
                           .build();

Por otra parte, para poder hacer la llamada del lado de servidor y validar el token necesitaremos implementar el servicio VSOOauthService, ya que la implementación OAuth de Visual Studio Online tiene ciertas peculiaridades.

public class VSOOauthService extends OAuth20ServiceImpl {

    private final DefaultApi20 api;
    private final OAuthConfig config;
    
    public VSOOauthService(DefaultApi20 api, OAuthConfig config) {
        super(api, config);
        
        this.api = api;
        this.config = config;
    }
    
    @Override
    public Token getAccessToken(Token requestToken, Verifier verifier)
    {
      OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint());
      request.addBodyParameter(VSOConstants.CLIENT_ASSERTION_TYPE, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
      request.addBodyParameter(VSOConstants.GRANT_TYPE, "urn:ietf:params:oauth:grant-type:jwt-bearer");
      request.addBodyParameter(VSOConstants.CLIENT_ASSERTION, config.getApiSecret());
      request.addBodyParameter(VSOConstants.ASSERTION, verifier.getValue());
      request.addBodyParameter(OAuthConstants.REDIRECT_URI, config.getCallback());
      request.addHeader("Content-type", "application/x-www-form-urlencoded");
      config.log(request.getCompleteUrl());
      Response response = request.send();
      return api.getAccessTokenExtractor().extract(response.getBody());
    }
}

Esta clase nos permite construir una petición POST (que hemos visto antes en que nos permitirá validar el código que hemos recibido desde Visual Studio Online utilizando el secret de nuestra aplicación y las claves específicas que nos solicita la aplicación, enviar la petición y convertir el resultado en un Token para futuras peticiones.

Creando la web para conectarnos al servicio

Una vez que tenemos el servicio creado para obtener el token, vamos a crear una pequeña página para poder hacer el proceso completo. Para ello utilizaremos spark, un proyecto inspirado en Sinatra que nos permite crear aplicaciones web de una manera rápida y sencilla:


        OAuthService service = new ServiceBuilder()
                           .provider(VSOnlineApi.class)
                           .apiKey("KEY")
                           .apiSecret("SECRET")
                           .callback("https://rlbisbe.dev:4567/callback")
                           .signatureType(SignatureType.QueryString)
                           .debugStream(System.out)
                           .debug()
                           .build();
        
        get("/auth", (req, res) -> {
            res.redirect(service.getAuthorizationUrl(EMPTY_TOKEN));
            return null;
        });
        
         get("/callback", (req, res) -> {

             String code = req.queryParams("code");
             Verifier verifier = new Verifier(code);
             Token token = service.getAccessToken(EMPTY_TOKEN, verifier);
             return token;
        });

Con el sódigo anterior podemos ver dos puntos de entrada, /auth, que realiza la redirección al servicio, y /callback, que recoge el resultado de la redirección, parsea el código del resultado, solicita el token utilizando el servicio que hemos creado anteriormente y muestra el token como resultado:

Token[CODIFICADOENHEXADECIMAL, ]

Consideraciones adicionales: SSL

Visual Studio Online requiere que la url de retorno esté bajo el protocolo SSL y no admite localhost, con lo cual para pruebas lo que podemos hacer es crear un certificado autofirmado utilizando la utilidad Keytool de Java:

keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass password -validity 360 -keysize 2048

Además deberemos importar dicha keytool dentro de nuestro proyecto para poder utilizar HTTPS:

        SparkBase.setSecure("C:\\Users\\Roberto\\Documents\\NetBeansProjects\\OauthClientTest\\keystore.jks", "password", null, null);

Finalmente, como no admite localhost, una solución puede ser utilizar el nombre DNS de nuestro ordenador de pruebas, o bien mediante el fichero hosts crear una entrada para 127.0.0.1, en mi caso rlbisbe.dev

Documentación

Y esto son las Interactive Rooms

En las últimas semanas he estado trabajando junto con el resto del equipo de VS Anywhere en una funcionalidad completamente nueva para nuestro Web Workspace, que hemos denominado Interactive Rooms.

Con ella, y aprovechando la recientemente presentada API de Visual Studio Online, podemos iniciar sesiones de colaboración directamente desde el navegador conectados con un repositorio Git o TFSVC, algo que, hasta ahora, resultaba imposible.

En este artículo veremos qué tecnologías y servicios hemos empleado para poder hacer posible este producto.

Obteniendo la información de Visual Studio Online

La API REST de Visual Studio Online, presentada en el pasado TechEd, permite acceder a la información de los ficheros almacenados en un repositorio, para poder comenzar una sesión en un punto específico de la historia del proyecto. Entre la información a la que podemos acceder están:

  • Team projects del portal de Visual Studio
  • Commits de un team project específico
  • Estado del repositorio en un commit específico, es decir, acceso a los ficheros.
  • Acceso a la Team Room del Team Project, para poder emitir mensajes.

Con esto, mostramos esta pantalla para que el usuario seleccione los datos antes de comenzar la sesión:

session

La autenticación está basada en OAuth2, lo que permite, una vez autorizado, poder realizar llamadas a la API sin tener que solicitar el usuario y la contraseña una y otra vez. El funcionamiento de OAuth va asociado a dos tokens, de autorización y de refresco, para asegurar la integridad de la cuenta en caso de que alguno de ellos se vea comprometido.

connectToVSO

Estos tokens, en nuestro caso, se asocian a la cuenta de usuario de VS Anywhere.

Almacenamiento y caché

Cuando creamos una sesión guardamos un snapshot de esta primera versión, que no contiene cambios. Esta snapshot se almacena dentro de Windows Azure Storage, concretamente dentro de un blob, que tiene esta estructura:

  • 123456789
    • 123456789000
    • 235123789021
    • 563473568561

Si accedemos desde el Azure Storage Explorer podemos ver qué se está almacenando:

blobs

Esta estructura de árbol nos permite obtener el histórico de una sesión y poder agregar nuevos items usando los ticks como clave. De esta manera podemos mostrar la fecha de creación sin necesidad de acceder al contenido del blob.

El resto de información almacenada (serializada como JSON) está relacionada con el tipo de autenticación, el team project, el commit o la lista de ficheros que se ha usado para poder re-crear la sesión en el futuro.

Para mantener una caché de sesiones activas usamos Redis, que nos permite un almacenamiento clave-valor con un acceso increíblemente rápido.

Descargando los datos

De la misma manera que podemos crear un snapshot, también podemos descargar el estado actual del proyecto, para integrarlo en nuestro workspace de git o TFS local, descargando un fichero que contiene solamente los documentos abiertos y los editados, siendo el resultado el que se muestra:

download

El fichero .zip se genera sobre la marcha, usando la información actual de la sesión y las opciones de compresión de .net 4.5 (ver más en MSDN).

Conclusiones

Mediante el uso de Azure Storage, Redis, y las API de Visual Studio Online hemos podido crear una funcionalidad adicional que complementa, por una parte el Web Workspace, y por otra el propio Visual Studio Online, dando nuevas opciones de colaboración.

La API, aún en estado beta, está bastante avanzada y productos como UserVoice o Zapier la están integrando dentro de sus propias soluciones.

¿Cómo empiezo?

Para poder empezar a usar las Interactive Rooms, solamente necesitas VS Anywhere 3.0.0.157 y una cuenta en Visual Studio Online. Puedes comenzar desde el propio Visual Studio siempre que:

  • Estés conectado a Visual Studio Online
  • El proyecto sea C# y esté en control de versiones
  • El team project esté conectado a visualstudio.com

Si se cumplen esos dos requisitos, las opciones se mostrarán al hacer click derecho sobre un proyecto:

options

Información y registro

Puedes encontrar más información (y registrarte) en la web de Interactive Rooms