1
\$\begingroup\$

I have code for a class that will manage an OAuth authentication key, included below. Because this class's functionality means it needs to be considered with regards to security, I wanted to check to make sure that all is as it should be (or figure out what ought to be changed), and there aren't any extra risks anywhere. I'm looking for advice on best practice, and I am reasonably sure that there aren't any major bugs in it.

This code will end up running as part of a daemon used to update a database of reports, just a simple tool for automating a task currently performed by hand, so it doesn't need to be bullet proof. Nor does it need to be as secure as you need for an application that will be distributed to hundreds of customers. I just need to be sure it isn't likely to cause our server to become compromised, and that it isn't too easy to break.

import java.awt.Desktop;
import java.io.*;
import java.net.*;

import com.box.boxjavalibv2.BoxClient;
import com.box.boxjavalibv2.dao.BoxOAuthToken;
import com.box.boxjavalibv2.exceptions.*;
import com.box.boxjavalibv2.requests.requestobjects.BoxOAuthRequestObject;
import com.box.restclientv2.exceptions.BoxRestException;


/**
 * This class handles the storage and use of authentication keys, to
 * simplify the process of obtaining n authenticated client.  This class
 * will store refresh keys in a file, so that it can authenticate a client
 * without needing for user intervention.
 * <p>
 * Copyright 2013 Mallick Mechanical, Inc.
 * 
 * @author Anson Mansfield
 */
public class Authenticator {

    /**
     * Constructs an {@code Authenticator} for use obtaining
     * authenticated {@Code BoxClient}s
     * 
     * @param key The OAuth client id and application key.
     * @param secret The OAuth client secret.
     * @param authFile The file to be used for storing authentications
     * for later use.
     */
    public Authenticator(String key, String secret, File authFile){
        this.key = key;
        this.secret = secret;
        this.authFile = authFile;
    }

    /**
     * Constructs a new {@Code BoxClient} object, authenticates it,
     * and returns it.
     */
    public BoxClient getAuthenticatedClient(){
        BoxClient client = new BoxClient(key,secret);

        client.authenticate(getToken(client));

        return client;

    }


    private final String host = "http://localhost";
    private final int port = 4000;

    private final String key, secret;
    private final File authFile;

    private final String url =
            "https://www.box.com/api/oauth2/authorize?response_type=code&client_id=";

    /**
     * Obtains a token that can be used to authenticate the box client,
     * and stores its refresh value in a file, so it can be used later.
     * @param client The client to obtain a token for.
     * @return A token that can be used to authenticate the client, or
     * {@code null} if one could not be obtained.
     */
    private BoxOAuthToken getToken(BoxClient client){

        BoxOAuthToken token = null;
        try{
            if((token = getOldToken(client)) != null) return token;

            if((token = getNewToken(client)) != null) return token;

            return token;

        }finally{
            writeNewToken(token);
        }


    }

    /**
     * Attempts to write a token's refresh token to a file.
     * @param token The token whose refresh value is to be written.
     */
    private void writeNewToken(BoxOAuthToken token) {
        if(token != null)
            try(BufferedWriter out = new BufferedWriter(new FileWriter(authFile))){
                out.write(token.getRefreshToken());
            }catch(IOException ex){
                System.out.println("couldn't update new token");
            }
    }

    /**
     * Reads the last session's refresh token from a file and attempts
     * to get a new authentication token with it.
     * @param client The client for which the authentication token is for.
     * @return The token obtained from the refresh, or {@code null} if one
     * could not be obtained.
     */
    private BoxOAuthToken getOldToken(BoxClient client) {

        System.out.println("attempting to use old token");

        BoxOAuthToken token = null;

        try(BufferedReader in = new BufferedReader(new FileReader(authFile))){
            token = client.getOAuthManager().refreshOAuth(
                    BoxOAuthRequestObject.refreshOAuthRequestObject(
                            in.readLine(), key, secret
                            ));
            System.out.println("refreshed old token");
        }catch(IOException ex){
            System.out.println("couldn't read old token");
        } catch(BoxRestException | BoxServerException | AuthFatalFailureException ex){
            System.out.println("couldn't refresh old token");
        }
        return token;
    }

    /**
     * Connects to the OAuth server and gets a new authentication token.
     * @param client The client to get a token for.
     * @return The new token obtained from the server, or {@code null} if one
     * could not be obtained.
     */
    private BoxOAuthToken getNewToken(BoxClient client) {

        System.out.println("attempting to get new token");

        BoxOAuthToken token = null;

        try {
            Desktop.getDesktop().browse(java.net.URI.create(url + key));
            token = client.getOAuthManager().createOAuth(
                    BoxOAuthRequestObject.createOAuthRequestObject(
                        getCode(), key, secret, host + port
                    ));

        } catch (BoxRestException | BoxServerException |  AuthFatalFailureException
                | IOException ex) {
            ex.printStackTrace();
            return null;
        }

        return token;
    }


    /**
     * This listens on the configured port for the code included in the callback.
     * It also deploys a script on the receiving socket to close the browser tab
     * navigating to it.
     * @return The authentication code to generate a token with.
     */
    private String getCode(){

        try (ServerSocket serverSocket = new ServerSocket(port);
                Socket socket = serverSocket.accept();
                BufferedReader in = new BufferedReader(
                        new InputStreamReader(socket.getInputStream()));
                PrintWriter out = new PrintWriter(socket.getOutputStream());){

            out.println("<script type=\"text/javascript\">");
            out.println(    "window.open('', '_self', '');");
            out.println(    "window.close();");
            out.println("</script>"); //closes tab

            while (true){
                String code = "";

                code = in.readLine ();
                System.out.println(code);
                String match = "code";
                int loc = code.indexOf(match);

                if( loc >0 ) {

                    int httpstr = code.indexOf("HTTP")-1;
                    code = code.substring(code.indexOf(match), httpstr);
                    String parts[] = code.split("=");
                    code=parts[1];
                    return code;

                } else {
                    // It doesn't have a code
                }
            }

        } catch (IOException | NullPointerException e) {
            return "";
        }
    }
}

Also, if anyone wants to have this code for their own use, just ask me, I just need to check with my boss to make sure it's OK before I can release it for use by anyone else (the company does mechanical contracting, not software development).

\$\endgroup\$
  • 1
    \$\begingroup\$ It seems crazy that you would need to call the Desktop browser to get the "code". But then again, I'm not sure what the "code" is. \$\endgroup\$ – toto2 Jun 27 '13 at 19:18
1
\$\begingroup\$

If this is running as a daemon - my understanding is that you mean as some kind of cron job or a background task, then who will be logging into your Box.com account to get the 'code' for the first time in the getCode?

Are you aware of the OAuth2 framework/protocol, if not, please read it here.

Basically, anyone having the access token gets the access as long as they also present your client_id and client_secret. Anyone presenting the refresh token will be granted a new access_token if they can give client_id_secret. As you are using java class running on local machine, it's not too hard to get the client_id and client_secret even if they are some constants defined in your class file.

I am not sure what kind of security you are looking for, but it seems that you are writing the refresh token to some file in the local system. If someone gets the refresh token, they can ask for an access token and have access to all your Box.com files.

Also, in case of Box.com the refresh token itself expires within 14 days, so if your daemon is not used regularly, then user will have to login to getCode again.

A more secure solution would be to have some kind of a web-server and register a redirect URL with your Box.com app which points to that server. Then from your web-server, you can get the 'code' and exchange it for the access+refresh token. Then store the refresh token securely (preferably in some db or behind some small app running on your web-server and not on each user machine) and fetch the refresh token to get the access_tokens.

However, it looks that your requirement is just to upload some MIS reports onto the Box cloud, so this looks good enough. Also, it appears that the user of this local machine actually knows the Box ID/password, so you probably don't have to worry about the user stealing your refresh token.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.