com.gsma.mobileconnect.demo.App.java Source code

Java tutorial

Introduction

Here is the source code for com.gsma.mobileconnect.demo.App.java

Source

/*
 *                                   SOFTWARE USE PERMISSION
 *
 *  By downloading and accessing this software and associated documentation files ("Software") you are granted the
 *  unrestricted right to deal in the Software, including, without limitation the right to use, copy, modify, publish,
 *  sublicense and grant such rights to third parties, subject to the following conditions:
 *
 *  The following copyright notice and this permission notice shall be included in all copies, modifications or
 *  substantial portions of this Software: Copyright  2016 GSM Association.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 *  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. YOU
 *  AGREE TO INDEMNIFY AND HOLD HARMLESS THE AUTHORS AND COPYRIGHT HOLDERS FROM AND AGAINST ANY SUCH LIABILITY.
 */

package com.gsma.mobileconnect.demo;

import com.fasterxml.jackson.databind.JsonNode;
import com.gsma.mobileconnect.discovery.DiscoveryException;
import com.gsma.mobileconnect.discovery.DiscoveryResponse;
import com.gsma.mobileconnect.discovery.IDiscovery;
import com.gsma.mobileconnect.impl.Factory;
import com.gsma.mobileconnect.oidc.*;
import com.gsma.mobileconnect.utils.KeyValuePair;
import com.gsma.mobileconnect.helpers.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Mobile Connect demonstration application.
 *
 * This is hosted as a spring boot application, but spring boot is not required to use the Mobile Connect SDK.
 *
 */
@Controller
@EnableAutoConfiguration
public class App {
    private static final String ERROR_ATTRIBUTE_NAME = "error";
    private static final String ERROR_DESCRIPTION_ATTRIBUTE_NAME = "error_description";

    private static final String ERROR_PAGE = "error_page";
    private static final String AUTHORIZED_PAGE = "authorized_page";
    private static final String START_AUTHORIZATION_PAGE = "request_authorization_page";
    private static final String START_DISCOVERY_PAGE = "request_discovery_page";

    private static final String SESSION_KEY = "demo:key";

    @Autowired
    private IOIDC oidc;

    @Autowired
    private IDiscovery discovery;

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    private MobileConnectConfig getMobileConnectConfig(HttpSession session) {
        // The Mobile Connect Interface methods expects a configuration object.
        // This can be unique per call or shared between calls as required.
        // Most of the values in the configuration object are optional.

        MobileConnectConfig mobileConnectConfig = null;
        synchronized (WebUtils.getSessionMutex(session)) {
            mobileConnectConfig = (MobileConnectConfig) session.getAttribute(SESSION_KEY);

            if (null == mobileConnectConfig) {
                mobileConnectConfig = new MobileConnectConfig();

                // Registered application client id
                mobileConnectConfig.setClientId("f6a93eee");

                // Registered application client secret
                mobileConnectConfig.setClientSecret("e0db5e4fa07768d6459f989cda046acc");

                // Registered application url
                mobileConnectConfig.setApplicationURL("http://localhost:8080/mobile_connect");

                // URL of the Mobile Connect Discovery End Point
                mobileConnectConfig.setDiscoveryURL("http://discovery.sandbox.mobileconnect.io/v2/discovery");

                // URL to inform the Discovery End Point to redirect to, this should route to the "/discovery_redirect" handler below
                mobileConnectConfig
                        .setDiscoveryRedirectURL("http://localhost:8080/mobileconnect/discovery_redirect");

                // Authorization State would typically set to a unique value
                mobileConnectConfig.setAuthorizationState(MobileConnectInterface.generateUniqueString("state_"));

                // Authorization Nonce would typically set to a unique value
                mobileConnectConfig.setAuthorizationNonce(MobileConnectInterface.generateUniqueString("nonce_"));

                session.setAttribute(SESSION_KEY, mobileConnectConfig);
            }
        }
        return mobileConnectConfig;
    }

    /**
     * This is the endpoint to initiate mobile connect.
      * <p>
      * The client javascript makes a JSON call to this method to initiate mobile connect authorization.
      * Authorization is carried out per operator, so the first step is to determine the operator.
     *
      * @param servletRequest The request
      * @param servletResponse The response
      * @param servletSession The session
      * @return JSON object describing what to do next either error, display the operator selection page or the operator authorization page
      */
    @ResponseBody
    @RequestMapping("mobileconnect/start_discovery")
    MobileConnectStatus.ResponseJson startDiscovery(HttpServletRequest servletRequest,
            HttpServletResponse servletResponse, HttpSession servletSession) {
        MobileConnectConfig config = getMobileConnectConfig(servletSession);

        // This wraps the SDK to determine what the next step in the authorization process is: operator discovery or authorization with an operator.
        MobileConnectStatus mobileConnectStatus = MobileConnectInterface
                .callMobileConnectForStartDiscovery(discovery, config, servletRequest, servletResponse);

        if (mobileConnectStatus.isOperatorSelection()) {
            // The operator cannot be identified, the operator selection url is set
            log.debug("The operator is unknown, redirect to the operator selection url: "
                    + mobileConnectStatus.getUrl());
        } else if (mobileConnectStatus.isStartAuthorization()) {
            // The operator has been identified. The discovery response is available.
            log.debug("The operator has been identified without requiring operator selection");

            DiscoveryResponse discoveryResponse = mobileConnectStatus.getDiscoveryResponse();
            logDiscoveryResponse(discoveryResponse);
        } else {
            // An error occurred, the error, description and nested exception (optional) are available.
            logError(mobileConnectStatus);
        }

        return mobileConnectStatus.getResponseJson();
    }

    /**
     * This is called by the redirect from the operator selection service.
     * <p>
     * The identified operator is encoded in the query string.
     * <p>
     * It responds with either:
     * <ul>
     * <li>an error
     * <li>a request to display the operator selection again
     * <li>a request to move onto authorization.
     * </ul>
     * <p>
     * It is assumed that this is redirected to from the operator selection popup so the return is a web page that contains
     * javascript that calls the parent page to continue the authorization process (either redisplay the operator selection
     * pop up or start authorization with the identified operator).
     *
     * @param servletRequest The servlet request
     * @param servletResponse The servlet response
     * @param servletSession The session
     * @param model The spring model to populate the response jsp
     * @return A Web page, the page contains javascript to call the parent page to continue the authorization process.
     */
    @RequestMapping("mobileconnect/discovery_redirect")
    String discoveryRedirect(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            HttpSession servletSession, Model model) {
        MobileConnectConfig config = getMobileConnectConfig(servletSession);

        MobileConnectStatus mobileConnectStatus = MobileConnectInterface
                .callMobileConnectOnDiscoveryRedirect(discovery, config, servletRequest, servletResponse);

        if (mobileConnectStatus.isError()) {
            // An error occurred, the error, description and nested exception (optional) are available.
            logError(mobileConnectStatus);
        } else if (mobileConnectStatus.isStartDiscovery()) {
            // The operator could not be identified, restart the discovery process.
            log.debug("The operator could not be identified, need to restart the discovery process.");
        } else if (mobileConnectStatus.isStartAuthorization()) {
            // The operator has been identified. The discovery response is available.
            log.debug("The operator has been identified and the authorization process can begin");

            logDiscoveryResponse(mobileConnectStatus.getDiscoveryResponse());
        }

        return toPageDescription(mobileConnectStatus, model);
    }

    /**
     * This is called by the client javascript to initiate authorization with an operator.
     * <p>
     * This is typically called after the operator has been determined. The identified operator's discovery response is expected to
     * be stored in the session.
     * <p>
     * It will return a json object that contains an error, a request to initiate operator discovery or a url
     * to redirect to start authorization with the identified operator.
     *
     * @param servletRequest The request
     * @param servletSession The session
     * @return JSON object.
     */
    @ResponseBody
    @RequestMapping("mobileconnect/start_authorization")
    MobileConnectStatus.ResponseJson startAuthorization(HttpServletRequest servletRequest,
            HttpSession servletSession) {
        MobileConnectConfig config = getMobileConnectConfig(servletSession);

        MobileConnectStatus mobileConnectStatus = MobileConnectInterface
                .callMobileConnectForStartAuthorization(oidc, config, servletRequest);

        if (mobileConnectStatus.isError()) {
            // An error occurred, the error, description optionally an exception is available.
            log.debug("Failed starting the authorization process");
            logError(mobileConnectStatus);
        } else if (mobileConnectStatus.isStartDiscovery()) {
            // The operator could not be identified, start the discovery process.
            log.debug("The operator could not be identified, need to restart the discovery process.");
        } else if (mobileConnectStatus.isAuthorization()) {
            // The operator has been identified, the discovery response and authentication url is available.
            log.debug("The operator has been identified and the authorization process can start");
            log.debug("URL is: " + mobileConnectStatus.getUrl());
            logDiscoveryResponse(mobileConnectStatus.getDiscoveryResponse());
        }

        return mobileConnectStatus.getResponseJson();
    }

    /**
     * This is called by the redirect from the operator authentication function.
     * <p>
     * This contains information that allows the SDK to obtain an authorization token (PCR) directly from the operator.
     * <p>
     * The response is either a successful authorization, an error or a request to identify the operator.
     *
     * @param servletRequest The request
     * @param servletSession The session
     * @param model The spring model to populate the jsp page
     * @return A web page
     */
    @RequestMapping("/mobile_connect")
    String authorizationRedirect(HttpServletRequest servletRequest, HttpSession servletSession, Model model) {
        MobileConnectConfig config = getMobileConnectConfig(servletSession);

        MobileConnectStatus mobileConnectStatus = MobileConnectInterface
                .callMobileConnectOnAuthorizationRedirect(oidc, config, servletRequest);

        if (mobileConnectStatus.isError()) {
            // An error occurred, the error, description and (optionally) exception is available.
            log.debug("Authorization has failed");
            logError(mobileConnectStatus);
        } else if (mobileConnectStatus.isStartDiscovery()) {
            // The operator could not be identified, start the discovery process.
            log.debug("The operator could not be identified, need to restart the discovery process.");
        } else if (mobileConnectStatus.isComplete()) {
            // Successfully authenticated, ParsedAuthenticationResponse and RequestTokenResponse are available
            log.debug("Authorization has completed successfully");
            log.debug("PCR is "
                    + mobileConnectStatus.getRequestTokenResponse().getResponseData().getParsedIdToken().get_pcr());
            logAuthorized(mobileConnectStatus);
        }

        return toPageDescription(mobileConnectStatus, model);
    }

    @RequestMapping("mobileconnect/mobile_connect_error")
    String mobileConnectError(HttpServletRequest request, Model model) {
        model.addAttribute(ERROR_ATTRIBUTE_NAME, request.getParameter(ERROR_ATTRIBUTE_NAME));
        model.addAttribute(ERROR_DESCRIPTION_ATTRIBUTE_NAME,
                request.getParameter(ERROR_DESCRIPTION_ATTRIBUTE_NAME));
        return ERROR_PAGE;
    }

    @ExceptionHandler(Exception.class)
    public String handleException(Exception ex, Model model) {
        log.error("Uncaught exception", ex);
        model.addAttribute(ERROR_ATTRIBUTE_NAME, "internal error");
        return ERROR_PAGE;
    }

    /**
     * Map the MobileConnectStatus to a jsp page.
     * @param status The MobileConnectStatus to map.
     * @param model The spring model to populate the page.
     * @return The name of the jsp page to return
     */
    private String toPageDescription(MobileConnectStatus status, Model model) {
        if (status.isComplete()) {
            return AUTHORIZED_PAGE;
        }
        if (status.isStartDiscovery()) {
            return START_DISCOVERY_PAGE;
        }
        if (status.isStartAuthorization()) {
            return START_AUTHORIZATION_PAGE;
        }
        model.addAttribute(ERROR_ATTRIBUTE_NAME, status.getError());
        model.addAttribute(ERROR_DESCRIPTION_ATTRIBUTE_NAME, status.getDescription());
        return ERROR_PAGE;
    }

    private void logDiscoveryResponse(DiscoveryResponse response) {
        // The Discovery Response may be cached data as indicated by
        log.debug("Is the DiscoveryResponse from the cache: " + response.isCached());

        // The Discovery Response contains the response code (if not from cache)
        log.debug("Discovery response code: " + response.getResponseCode());

        // The Discovery Response contains the response headers (if not from cache).
        if (null != response.getHeaders()) {
            for (KeyValuePair kvp : response.getHeaders()) {
                log.debug(kvp.getKey() + ": " + kvp.getValue());
            }
        }

        // The Discovery Response contains the discovery JSON object
        JsonNode discoveryJson = response.getResponseData();
        log.debug("Serving operator is: " + discoveryJson.get("response").get("serving_operator").asText());
    }

    private void logAuthorized(MobileConnectStatus status) {
        ParsedAuthorizationResponse parsedAuthorizationResponse = status.getParsedAuthorizationResponse();
        if (null != parsedAuthorizationResponse) {
            log.debug("Code: " + parsedAuthorizationResponse.get_code());
            log.debug("State: " + parsedAuthorizationResponse.get_state());
        }
        RequestTokenResponse requestTokenResponse = status.getRequestTokenResponse();
        if (null != requestTokenResponse) {
            log.debug("Response Code: " + requestTokenResponse.getResponseCode());
            if (null != requestTokenResponse.getHeaders()) {
                for (KeyValuePair kvp : requestTokenResponse.getHeaders()) {
                    log.debug(kvp.getKey() + ": " + kvp.getValue());
                }
            }
            if (null != requestTokenResponse.getResponseData()) {
                RequestTokenResponseData responseData = requestTokenResponse.getResponseData();
                log.debug("Time Received: " + responseData.getTimeReceived().getTime());
                if (null != responseData.getParsedIdToken()) {
                    ParsedIdToken parsedIdToken = responseData.getParsedIdToken();
                    log.debug("Nonce: " + parsedIdToken.get_nonce());
                    log.debug("PCR: " + parsedIdToken.get_pcr());
                }
            }

        }
    }

    private void logError(MobileConnectStatus status) {
        log.debug("An error occurred: " + status.getError() + ": " + status.getDescription());
        Exception exception = status.getException();
        log.debug("Exception was: " + exception);

        // Depending on the exception more information may be available
        if (exception instanceof DiscoveryException) {
            DiscoveryException ex = (DiscoveryException) exception;
            log.debug(ex.getMessage());
            log.debug("URI: " + ex.getUri());
            log.debug("Response code: " + ex.getResponseCode());
            log.debug("Contents: " + ex.getContents());
            if (null != ex.getHeaders()) {
                for (KeyValuePair kvp : ex.getHeaders()) {
                    log.debug(kvp.getKey() + ": " + kvp.getValue());
                }
            }
        } else if (exception instanceof OIDCException) {
            OIDCException ex = (OIDCException) exception;
            log.debug(ex.getMessage());
            log.debug("URI: " + ex.getUri());
            log.debug("Response code: " + ex.getResponseCode());
            log.debug("Contents: " + ex.getContents());
            if (null != ex.getHeaders()) {
                for (KeyValuePair kvp : ex.getHeaders()) {
                    log.debug(kvp.getKey() + ": " + kvp.getValue());
                }
            }
        }
    }

    @Bean
    private IDiscovery getDiscovery() {
        log.debug("Created a discovery instance");
        return Factory.getDiscovery(Factory.getDefaultDiscoveryCache());
    }

    @Bean
    private IOIDC getOIDC() {
        log.debug("Created a oidc instance");
        return Factory.getOIDC();
    }

    public static void main(String[] args) throws Exception {
        SpringApplication.run(App.class, args);
    }

}