/*
 * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.wso2.carbon.device.mgt.ios.services.impl;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.cms.CMSException;
import org.wso2.carbon.apimgt.application.extension.exception.APIManagerException;
import org.wso2.carbon.base.MultitenantConstants;
import org.wso2.carbon.certificate.mgt.core.dto.SCEPResponse;
import org.wso2.carbon.certificate.mgt.core.exception.KeystoreException;
import org.wso2.carbon.certificate.mgt.core.scep.SCEPException;
import org.wso2.carbon.certificate.mgt.core.scep.TenantedDeviceWrapper;
import org.wso2.carbon.certificate.mgt.core.service.CertificateManagementService;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.DeviceManagementConstants;
import org.wso2.carbon.device.mgt.common.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.app.mgt.Application;
import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException;
import org.wso2.carbon.device.mgt.common.license.mgt.License;
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException;
import org.wso2.carbon.device.mgt.common.policy.mgt.Policy;
import org.wso2.carbon.device.mgt.common.policy.mgt.ProfileFeature;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.PolicyComplianceException;
import org.wso2.carbon.device.mgt.ios.api.utils.beans.DeviceIdentifierBean;
import org.wso2.carbon.device.mgt.ios.api.utils.beans.LoginBean;
import org.wso2.carbon.device.mgt.ios.api.utils.beans.ProfileBean;
import org.wso2.carbon.device.mgt.ios.api.utils.beans.TokenBean;
import org.wso2.carbon.device.mgt.ios.api.utils.util.ApplicationUtils;
import org.wso2.carbon.device.mgt.ios.api.utils.util.Message;
import org.wso2.carbon.device.mgt.ios.api.utils.util.OperationMapping;
import org.wso2.carbon.device.mgt.ios.apns.exception.APNSException;
import org.wso2.carbon.device.mgt.ios.core.bean.ChallengeTokenBean;
import org.wso2.carbon.device.mgt.ios.core.exception.IOSEnrollmentException;
import org.wso2.carbon.device.mgt.ios.core.exception.TenantConfigurationException;
import org.wso2.carbon.device.mgt.ios.core.impl.SCEPOperation;
import org.wso2.carbon.device.mgt.ios.core.publisher.TokenPersistence;
import org.wso2.carbon.device.mgt.ios.core.service.IOSEnrollmentService;
import org.wso2.carbon.device.mgt.ios.core.util.AppConfigurations;
import org.wso2.carbon.device.mgt.ios.core.util.TenantUtils;
import org.wso2.carbon.device.mgt.ios.exception.BadRequestException;
import org.wso2.carbon.device.mgt.ios.payload.dto.APNSStatus;
import org.wso2.carbon.device.mgt.ios.payload.dto.CertificateAttributes;
import org.wso2.carbon.device.mgt.ios.payload.dto.CheckInMessageType;
import org.wso2.carbon.device.mgt.ios.payload.dto.DeviceProperties;
import org.wso2.carbon.device.mgt.ios.payload.exception.PListException;
import org.wso2.carbon.device.mgt.ios.payload.exception.ProfileConfigurationException;
import org.wso2.carbon.device.mgt.ios.services.EnrollmentService;
import org.wso2.carbon.device.mgt.ios.util.ContentType;
import org.wso2.carbon.device.mgt.ios.util.DeviceUtils;
import org.wso2.carbon.device.mgt.ios.util.EnrollmentUtils;
import org.wso2.carbon.device.mgt.ios.util.IOSServiceUtils;
import org.wso2.carbon.device.mgt.ios.util.OAuthUtils;
import org.wso2.carbon.device.mgt.ios.util.OperationUtils;
import org.wso2.carbon.device.mgt.ios.util.PolicyUtils;
import org.wso2.carbon.policy.mgt.common.PolicyManagementException;
import org.wso2.carbon.policy.mgt.core.util.PolicyManagementConstants;
import org.wso2.carbon.user.api.UserStoreException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.lang.reflect.Type;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

public class EnrollmentServiceImpl implements EnrollmentService {
    private static final Log log = LogFactory.getLog(EnrollmentServiceImpl.class);

    @GET
    @Produces({MediaType.WILDCARD})
    @Path("/ca")
    public void getCA(@Context HttpServletResponse response) throws Exception {

        if (log.isDebugEnabled()) {
            log.debug("Requesting CA certificate");
        }

        try {
            CertificateManagementService certificateManagementService = IOSServiceUtils.
                    getCertificateManagementService();
            Certificate certificateCA = certificateManagementService.getCACertificate();

            //TODO Temp fix to get this working. Make this a proper API.
            byte[] data = certificateCA.getEncoded();
            response.setContentType(ContentType.X_X509_CA_CERT);
            response.setContentLength(data.length);
            response.getOutputStream().write(data);

        } catch (CertificateEncodingException e) {
            log.error("Error occurred in certificate encoding", e);
        } catch (KeystoreException e) {
            log.error("Keystore error occurred while enrolling the iOS device", e);
        } catch (IOSEnrollmentException e) {
            log.error("Error occurred while enrolling the iOS device", e);
        }

    }

    @POST
    @Consumes({MediaType.APPLICATION_JSON})
    @Produces({MediaType.APPLICATION_JSON})
    @Path("/authenticate")
    public Response authenticate(LoginBean loginBean) {

        if (log.isDebugEnabled()) {
            log.debug("Authentication endpoint called");
        }

        try {
            IOSServiceUtils iosServiceUtils = new IOSServiceUtils();
            if (iosServiceUtils.authenticate(loginBean.getUsername(), loginBean.getPassword(),
                    loginBean.getTenantDomain())) {
                DeviceIdentifierBean deviceIdentifierBean = OAuthUtils.getOAuthToken(loginBean);
                String challengeToken = EnrollmentUtils.generateRandomToken();
                ChallengeTokenBean challengeTokenBean = new ChallengeTokenBean();
                challengeTokenBean.setChallengeToken(challengeToken);
                challengeTokenBean.setDeviceID("");
                challengeTokenBean.setUsername(loginBean.getUsername());
                challengeTokenBean.setDomain(loginBean.getTenantDomain());
                challengeTokenBean.setRefreshToken(deviceIdentifierBean.getRefreshToken());
                challengeTokenBean.setAccessToken(deviceIdentifierBean.getAccessToken());
                challengeTokenBean.setClient(deviceIdentifierBean.getClientCredentials());
                EnrollmentUtils.persistChallengeToken(challengeTokenBean);

                TokenBean tokenBean = new TokenBean();
                tokenBean.setChallengeToken(challengeToken);
                return Response.ok(tokenBean).build();
            }
        } catch (IOSEnrollmentException e) {
            log.error("Error occurred while enrolling the iOS device", e);
            return Response.serverError().build();
        } catch (APIManagerException e) {
            log.error("Error occurred while enrolling the iOS device", e);
            return Response.serverError().build();
        } catch (UserStoreException e) {
            log.error("Error occurred while initializing the userstore for the tenant: " + loginBean.getTenantDomain()
                    , e);
            return Response.serverError().build();
        }

        return Response.status(Response.Status.UNAUTHORIZED).build();
    }

    @POST
    @Consumes({ MediaType.APPLICATION_JSON })
    @Produces({ ContentType.APPLE_ASPEN_CONFIG })
    @Path("/enroll")
    public Response getMobileConfigurations(LoginBean loginBean) {

        if (log.isDebugEnabled()) {
            log.debug("Requesting mobile configurations");
        }

        try {
            IOSServiceUtils iosServiceUtils = new IOSServiceUtils();
            if (!iosServiceUtils
                    .authenticate(loginBean.getUsername(), loginBean.getPassword(), loginBean.getTenantDomain())) {
                return Response.status(Response.Status.UNAUTHORIZED).build();
            }

            IOSEnrollmentService enrollmentService = IOSServiceUtils.getEnrollmentService();
            String tenantDomain = loginBean.getTenantDomain();
            if (StringUtils.isEmpty(tenantDomain)) {
                tenantDomain = TenantUtils.getTenantDomainName();
            }
            String mobileConfiguration = enrollmentService
                    .generateMobileConfigurations(loginBean.getChallengeToken(), tenantDomain);
            byte signedData[] = enrollmentService.getSignedData(mobileConfiguration.getBytes());

            return Response.ok(signedData).build();
        } catch (IOSEnrollmentException e) {
            log.error("Error occurred while enrolling the iOS device", e);
        }

        return Response.serverError().build();
    }

    @GET
    @Path("/enrolled")
    public Response isEnrolled(@QueryParam("deviceid") String deviceId) {

        if (log.isDebugEnabled()) {
            log.debug("Invoking isEnrolled method");
        }

        DeviceIdentifier deviceIdentifier = new DeviceIdentifier();
        deviceIdentifier.setId(deviceId);
        deviceIdentifier.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_IOS);

        try {
            TenantedDeviceWrapper tenantedDeviceWrapper = IOSServiceUtils.getSCEPManager().
                    getValidatedDevice(deviceIdentifier);

            if (tenantedDeviceWrapper != null && tenantedDeviceWrapper.getTenantDomain() != null &&
                    tenantedDeviceWrapper.getTenantId() != MultitenantConstants.INVALID_TENANT_ID) {
                try {
                    PrivilegedCarbonContext.startTenantFlow();
                    PrivilegedCarbonContext privilegedCarbonContext = PrivilegedCarbonContext
                            .getThreadLocalCarbonContext();
                    privilegedCarbonContext.setTenantId(tenantedDeviceWrapper.getTenantId());
                    privilegedCarbonContext.setTenantDomain(tenantedDeviceWrapper.getTenantDomain());
                    privilegedCarbonContext
                            .setUsername(tenantedDeviceWrapper.getDevice().getEnrolmentInfo().getOwner());

                    IOSEnrollmentService enrollmentService = IOSServiceUtils.getEnrollmentService();
                    if (enrollmentService.isEnrolled(deviceIdentifier)) {
                        return Response.ok().build();
                    } else {
                        return Response.status(Response.Status.NOT_FOUND).build();
                    }
                } finally {
                    PrivilegedCarbonContext.endTenantFlow();
                }
            }
        } catch (SCEPException e) {
            log.error("Error occurred while fetching the device", e);
        } catch (IOSEnrollmentException e) {
            log.error("Error occurred while enrolling the iOS device", e);
        }
        return Response.serverError().build();
    }

    @POST
    @Produces({ContentType.APPLE_ASPEN_CONFIG, MediaType.APPLICATION_JSON})
    @Consumes({MediaType.WILDCARD})
    @Path("/profile")
    public Response getProfileRequest(InputStream inputStream) {

        if (log.isDebugEnabled()) {
            log.debug("Executing profile request");
        }

        ChallengeTokenBean challengeTokenBean;
        IOSEnrollmentService enrollmentService = null;
        InputStream[] clonedInputStream = null;
        String username = null;
        String tenantDomain = null;
        int tenantId = MultitenantConstants.SUPER_TENANT_ID;

        try {
            OperationUtils operationUtils = new OperationUtils();
            clonedInputStream = operationUtils.cloneInputStream(inputStream);
            enrollmentService = IOSServiceUtils.getEnrollmentService();
            String challengeToken = enrollmentService.extractChallengeTokenFromProfile(clonedInputStream[0]);
            String deviceIdentifier = enrollmentService.extractDeviceIdentifierFromProfile(clonedInputStream[1]);
            if (StringUtils.isNotEmpty(challengeToken) && StringUtils.isNotEmpty(deviceIdentifier)) {
                challengeTokenBean = new ChallengeTokenBean();
                challengeTokenBean.setChallengeToken(challengeToken);
                challengeTokenBean.setDeviceID(deviceIdentifier);
                EnrollmentUtils.persistChallengeToken(challengeTokenBean);
            }

            if (StringUtils.isNotEmpty(challengeToken)) {
                challengeTokenBean = EnrollmentUtils.getTokenEntry(challengeToken);
            } else {
                challengeTokenBean = EnrollmentUtils.getTokenEntryByDeviceId(deviceIdentifier);
            }

            if (challengeTokenBean != null) {
                username = challengeTokenBean.getUsername();
                tenantDomain = challengeTokenBean.getDomain();

                if (StringUtils.isNotEmpty(tenantDomain) && !MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.
                        equals(tenantDomain)) {
                    tenantId = IOSServiceUtils.getTenantID(tenantDomain);
                }
            }
        } catch (IOSEnrollmentException e) {
            log.error("Error occurred while enrolling the iOS device", e);
        } catch (PListException e) {
            log.error("Error occurred while reading input stream payload", e);
        }

        if (StringUtils.isNotEmpty(username)) {
            try {
                PrivilegedCarbonContext.startTenantFlow();
                PrivilegedCarbonContext privilegedCarbonContext = PrivilegedCarbonContext.
                        getThreadLocalCarbonContext();
                if (StringUtils.isNotEmpty(tenantDomain)) {
                    privilegedCarbonContext.setTenantDomain(tenantDomain);
                } else {
                    privilegedCarbonContext.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);
                }
                privilegedCarbonContext.setTenantId(tenantId);
                privilegedCarbonContext.setUsername(username);

                Message message = new Message();
                TenantUtils tenantUtils = new TenantUtils();

                CertificateAttributes certificateAttributes = tenantUtils.getCertificateAttributes();
                String topicID = tenantUtils.getTopicID();
                String tenantDisplayName = tenantUtils.getTenantDisplayName();

                if (tenantDisplayName == null || tenantDisplayName.isEmpty()) {
                    tenantDisplayName = "";
                }

                if (certificateAttributes == null) {
                    message.setErrorMessage("Certificate attributes are not configured");
                    throw new BadRequestException(message, MediaType.APPLICATION_JSON_TYPE);
                }

                if (topicID == null) {
                    message.setErrorMessage("Topic ID is not configured");
                    throw new BadRequestException(message, MediaType.APPLICATION_JSON_TYPE);
                }

                if (enrollmentService != null) {
                    byte[] signedData = enrollmentService
                            .handleProfileRequest(clonedInputStream[2], tenantDisplayName, topicID,
                                    certificateAttributes);
                    return Response.ok(signedData).build();
                }
            } catch (IOSEnrollmentException e) {
                log.error("Error occurred while enrolling the iOS device", e);
            } catch (DeviceManagementException e) {
                log.error("Error occurred while fetching tenant configurations", e);
            } catch (TenantConfigurationException e) {
                log.error("Error occurred while reading registry entries", e);
            } finally {
                PrivilegedCarbonContext.endTenantFlow();
            }
        }
        return Response.serverError().build();
    }

    @GET
    @Path("/scep")
    public Response scepRequest(@QueryParam("operation") String operation, @QueryParam("message") String message) {

        if (log.isDebugEnabled()) {
            log.debug("Invoking SCEP operation " + operation);
        }

        if (SCEPOperation.GET_CA_CERT.getValue().equals(operation)) {

            if (log.isDebugEnabled()) {
                log.debug("Invoking GetCACert");
            }

            try {
                CertificateManagementService certificateManagementService = IOSServiceUtils.
                        getCertificateManagementService();
                SCEPResponse scepResponse = certificateManagementService.getCACertSCEP();
                Response.ResponseBuilder responseBuilder;

                switch (scepResponse.getResultCriteria()) {
                    case CA_CERT_FAILED:
                        log.error("CA cert failed");
                        responseBuilder = Response.serverError();
                        break;
                    case CA_CERT_RECEIVED:

                        if (log.isDebugEnabled()) {
                            log.debug("CA certificate received in GetCACert");
                        }

                        responseBuilder = Response.ok(scepResponse.getEncodedResponse(), ContentType.X_X509_CA_CERT);
                        break;
                    case CA_RA_CERT_RECEIVED:

                        if (log.isDebugEnabled()) {
                            log.debug("CA and RA certificates received in GetCACert");
                        }

                        responseBuilder = Response.ok(scepResponse.getEncodedResponse(), ContentType.X_X509_CA_RA_CERT);
                        break;
                    default:
                        log.error("Invalid SCEP request");
                        responseBuilder = Response.serverError();
                        break;
                }

                return responseBuilder.build();
            } catch (IOSEnrollmentException e) {
                log.error("Error occurred while enrolling the iOS device", e);
            } catch (KeystoreException e) {
                log.error("Keystore error occurred while enrolling the iOS device", e);
            }

        } else if (SCEPOperation.GET_CA_CAPS.getValue().equals(operation)) {

            if (log.isDebugEnabled()) {
                log.debug("Invoking GetCACaps");
            }

            try {
                CertificateManagementService certificateManagementService = IOSServiceUtils.
                        getCertificateManagementService();
                byte caCaps[] = certificateManagementService.getCACapsSCEP();

                return Response.ok(caCaps, MediaType.TEXT_PLAIN).build();
            } catch (IOSEnrollmentException e) {
                log.error("Error occurred while enrolling the iOS device", e);
            }

        } else {
            log.error("Invalid SCEP operation " + operation);
        }

        return Response.serverError().build();
    }

    @POST
    @Consumes({ContentType.X_PKI_MESSAGE})
    @Path("/scep")
    public Response scepRequestPost(@QueryParam("operation") String operation, InputStream inputStream) {

        if (log.isDebugEnabled()) {
            log.debug("Invoking SCEP operation " + operation);
        }

        if (SCEPOperation.PKI_OPERATION.getValue().equals(operation)) {

            if (log.isDebugEnabled()) {
                log.debug("Invoking PKIOperation");
            }

            try {
                CertificateManagementService certificateManagementService = IOSServiceUtils.
                        getCertificateManagementService();
                byte pkiMessage[] = certificateManagementService.getPKIMessageSCEP(inputStream);

                return Response.ok(pkiMessage, ContentType.X_PKI_MESSAGE).build();
            } catch (IOSEnrollmentException e) {
                log.error("Error occurred while enrolling the iOS device", e);
            } catch (KeystoreException e) {
                log.error("Keystore error occurred while enrolling the iOS device", e);
            }

        }

        return Response.serverError().build();
    }

    @PUT
    @Produces({MediaType.WILDCARD})
    @Consumes({MediaType.WILDCARD})
    @Path("/checkin")
    public Response checkInRequest(InputStream inputStream) {
        if (log.isDebugEnabled()) {
            log.debug("Invoking check-in url");
        }

        CheckInMessageType checkInMessageType = null;
        try {
            StringWriter stringWriter = new StringWriter();
            IOUtils.copy(inputStream, stringWriter, AppConfigurations.UTF_8);
            String contentString = stringWriter.toString();
            IOSEnrollmentService enrollmentService = IOSServiceUtils.getEnrollmentService();
            checkInMessageType = enrollmentService.extractTokens(contentString);
            if (log.isDebugEnabled()) {
                log.debug("CheckIn url message type is " + checkInMessageType.getMessageType());
            }
        } catch (PListException e) {
            log.error("Error occurred when extracting plist elements in check-in request", e);
        } catch (IOSEnrollmentException e) {
            log.error("Error occurred while enrolling the iOS device", e);
        } catch (IOException e) {
            log.error("Input stream cannot be parsed in check-in request", e);
        }

        if (checkInMessageType != null) {
            DeviceIdentifier deviceIdentifier = new DeviceIdentifier();
            deviceIdentifier.setId(checkInMessageType.getDeviceIdentifier());
            deviceIdentifier.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_IOS);
            try {
                TenantedDeviceWrapper tenantedDeviceWrapper = IOSServiceUtils.getSCEPManager()
                        .getValidatedDevice(deviceIdentifier);
                if (tenantedDeviceWrapper != null && tenantedDeviceWrapper.getTenantDomain() != null &&
                        tenantedDeviceWrapper.getTenantId() != MultitenantConstants.INVALID_TENANT_ID) {
                    try {
                        PrivilegedCarbonContext.startTenantFlow();
                        PrivilegedCarbonContext privilegedCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
                        privilegedCarbonContext.setTenantDomain(tenantedDeviceWrapper.getTenantDomain());
                        privilegedCarbonContext.setTenantId(tenantedDeviceWrapper.getTenantId());
                        privilegedCarbonContext.setUsername(tenantedDeviceWrapper.getDevice().getEnrolmentInfo().getOwner());
                        if (log.isDebugEnabled()) {
                            log.debug("Starting tenant flow with tenant: " + tenantedDeviceWrapper.getTenantDomain());
                        }
                        Message message = new Message();

                        if (checkInMessageType.getMessageType() == CheckInMessageType.MessageType.CHECKOUT) {
                            if (log.isDebugEnabled()) {
                                log.debug("UDID " + checkInMessageType.getDeviceIdentifier());
                            }

                            List<? extends Operation> operations = IOSServiceUtils.getDeviceManagementService().getOperations(deviceIdentifier);
                            if (operations != null) {
                                for (Operation operation : operations) {
                                    operation.setStatus(Operation.Status.COMPLETED);
                                    IOSServiceUtils.getDeviceManagementService()
                                            .updateOperation(deviceIdentifier, operation);
                                }
                            }

                            TokenPersistence tokenPersistence = new TokenPersistence();
                            tokenPersistence.disEnrollDevice(checkInMessageType.getDeviceIdentifier());

                            return Response.ok().build();
                        } else if (checkInMessageType.getMessageType() == CheckInMessageType.MessageType.TOKEN_UPDATE) {
                            if (log.isDebugEnabled()) {
                                log.debug("Magic Token " + checkInMessageType.getPushMagic());
                                log.debug("Token " + checkInMessageType.getToken());
                                log.debug("Unlock Token " + checkInMessageType.getUnlockToken());
                                log.debug("UDID " + checkInMessageType.getDeviceIdentifier());
                            }
                            TokenPersistence tokenPersistence = new TokenPersistence();
                            message.setErrorMessage("Device ID not found");

                            DeviceProperties deviceProperties = new DeviceProperties();
                            deviceProperties.setChallenge(null);
                            deviceProperties.setDeviceIdentifier(checkInMessageType.getDeviceIdentifier());

                            tokenPersistence.savePushTokens(deviceProperties, checkInMessageType.getToken(),
                                    checkInMessageType.getPushMagic(), checkInMessageType.getUnlockToken());

                            PolicyUtils policyUtils = new PolicyUtils();
                            policyUtils.enforceEffectivePolicy(checkInMessageType.getDeviceIdentifier());
                            return Response.ok().build();
                        } else if (checkInMessageType.getMessageType() == CheckInMessageType.MessageType.AUTHENTICATE) {
                            return Response.ok().build();
                        }
                    } catch (DeviceManagementException e) {
                        log.error("Error occurred when saving push token in check-in request", e);
                    } catch (APNSException e) {
                        log.error("Error occurred while sending a push notification message to APNS", e);
                    } catch (OperationManagementException e) {
                        log.error("Error occurred while updating unenroll status", e);
                    } finally {
                        PrivilegedCarbonContext.endTenantFlow();
                    }
                }
            } catch (SCEPException e) {
                log.error("Error occurred while fetching the device", e);
            } catch (IOSEnrollmentException e) {
                log.error("Error occurred while enrolling the iOS device", e);
            }
        }

        return Response.serverError().build();
    }

    @PUT
    @Produces({MediaType.WILDCARD})
    @Consumes({MediaType.WILDCARD})
    @Path("/server")
    public Response serverRequest(@Context HttpServletRequest request, InputStream inputStream) throws CMSException {
        if (log.isDebugEnabled()) {
            log.debug("Invoking server URL");
        }
        OperationUtils operationUtils = new OperationUtils();
        APNSStatus apnsStatus = null;
        DeviceIdentifier deviceIdentifier = null;
        String contentString = null;
        Operation operation = null;

        try {
            String headerMDMSignature = request.getHeader(IOSServiceUtils.HEADER_MDM_SIGNATURE);
            Message message = new Message();

            StringWriter stringWriter = new StringWriter();
            X509Certificate certificate = IOSServiceUtils.getCertificateManagementService().
                    extractCertificateFromSignature(headerMDMSignature);

            IOUtils.copy(inputStream, stringWriter, AppConfigurations.UTF_8);
            contentString = stringWriter.toString();

            IOSEnrollmentService enrollmentService = IOSServiceUtils.getEnrollmentService();
            apnsStatus = enrollmentService.extractAPNSResponse(contentString);

            DeviceUtils deviceUtils = new DeviceUtils();
            deviceIdentifier = deviceUtils.fetchDeviceIdentifierInstance(apnsStatus.getDeviceIdentifier(), message,
                    MediaType.APPLICATION_JSON_TYPE);
            Device device = IOSServiceUtils.getDeviceManagementService().getDevice(deviceIdentifier);

            TenantedDeviceWrapper tenantedDeviceWrapper = IOSServiceUtils.getSCEPManager().
                    getValidatedDevice(deviceIdentifier);
            if (tenantedDeviceWrapper != null && tenantedDeviceWrapper.getTenantDomain() != null &&
                    tenantedDeviceWrapper.getTenantId() != MultitenantConstants.INVALID_TENANT_ID) {
                try {
                    PrivilegedCarbonContext.startTenantFlow();
                    PrivilegedCarbonContext privilegedCarbonContext = PrivilegedCarbonContext
                            .getThreadLocalCarbonContext();
                    privilegedCarbonContext.setTenantDomain(tenantedDeviceWrapper.getTenantDomain());
                    privilegedCarbonContext.setTenantId(tenantedDeviceWrapper.getTenantId());
                    privilegedCarbonContext
                            .setUsername(tenantedDeviceWrapper.getDevice().getEnrolmentInfo().getOwner());
                    if (log.isDebugEnabled()) {
                        log.debug("Starting tenant flow with tenant: " + tenantedDeviceWrapper.getTenantDomain());
                    }

                    if (APNSStatus.APNSResponseStatus.Acknowledged.toString().equals(apnsStatus.getStatus())) {
                        operation = new Operation();
                        String queryResponse = apnsStatus.getResponseData();

                        if (APNSStatus.APNSResponse.QueryResponses.toString().equals(apnsStatus.getOperation())) {
                            TokenPersistence tokenPersistence = new TokenPersistence();
                            tokenPersistence.saveDeviceInformation(deviceIdentifier, queryResponse);
                        } else if (APNSStatus.APNSResponse.InstalledApplicationList.toString().
                                equals(apnsStatus.getOperation())) {
                            ApplicationUtils applicationUtils = new ApplicationUtils();
                            List<Application> applications = applicationUtils.parseApplicationList(queryResponse);

                            operationUtils.updateApplicationList(applications, deviceIdentifier);
                        } else if (APNSStatus.APNSResponse.ProfileList.toString().equals(apnsStatus.getOperation())) {

                            Gson gson = new Gson();
                            Type type = new TypeToken<List<ProfileBean>>() {
                            }.getType();
                            List<ProfileBean> profiles = gson.fromJson(queryResponse, type);

                            boolean isCompliance = false;

                            for (ProfileBean profileBean : profiles) {
                                if (PolicyManagementConstants.POLICY_BUNDLE
                                        .equals(profileBean.getPayloadIdentifier())) {
                                    //TODO check one by one and do the compliance
                                    isCompliance = true;
                                    break;
                                }
                            }

                            Policy policy = IOSServiceUtils.getPolicyManagerService().
                                    getAppliedPolicyToDevice(deviceIdentifier);

                            if (policy != null) {
                                List<ProfileFeature> profileFeatures = policy.getProfile().getProfileFeaturesList();
                                List<ComplianceFeature> complianceFeatures = new ArrayList<ComplianceFeature>();

                                for (ProfileFeature profileFeature : profileFeatures) {
                                    ComplianceFeature complianceFeature = new ComplianceFeature();
                                    complianceFeature.setFeature(profileFeature);
                                    complianceFeature.setFeatureCode(profileFeature.getFeatureCode());
                                    complianceFeature.setCompliance(isCompliance);
                                    complianceFeatures.add(complianceFeature);
                                }

                                IOSServiceUtils.getPolicyManagerService()
                                        .checkCompliance(deviceIdentifier, complianceFeatures);
                            }

                        } else {
                            operation.setOperationResponse(queryResponse);
                        }

                        operation.setId(Integer.valueOf(apnsStatus.getCommandUUID()));
                        operation.setStatus(Operation.Status.COMPLETED);
                        operationUtils.updateOperation(operation, deviceIdentifier);
                    } else if (APNSStatus.APNSResponseStatus.Error.toString().equals(apnsStatus.getStatus())) {
                        operation = new Operation();
                        operation.setId(Integer.valueOf(apnsStatus.getCommandUUID()));
                        operation.setStatus(Operation.Status.ERROR);
                        operation.setOperationResponse(apnsStatus.getError());
                        operationUtils.updateOperation(operation, deviceIdentifier);
                    }

                    operation = operationUtils.getPendingOperation(apnsStatus.getDeviceIdentifier());

                    if (operation != null) {

                        if (operationUtils.isOneWayOperation(operation)) {
                            operation.setStatus(Operation.Status.COMPLETED);
                        } else {
                            operation.setStatus(Operation.Status.IN_PROGRESS);
                        }

                        operationUtils.updateOperation(operation, deviceIdentifier);

                        if (OperationMapping.ENTERPRISE_WIPE.getCode().equals(operation.getCode())) {
                            return Response.status(Response.Status.UNAUTHORIZED).build();
                        } else {
                            String operationPayload = operationUtils
                                    .generateOperationPayload(device, operation, certificate);

                            if (operationPayload != null && !operationPayload.isEmpty()) {
                                return Response.status(Response.Status.OK).entity(operationPayload.
                                        getBytes(AppConfigurations.UTF_8)).type(ContentType.APPLE_ASPEN_CONFIG).build();
                            }
                        }
                    }

                } catch (ProfileConfigurationException e) {
                    String errorMsg = "Error occurred when creating the profile for server request. Discarding the operation.";
                    log.error(errorMsg, e);
                    operationUtils
                            .updateOperationWithErrorStatus(apnsStatus.getCommandUUID(), errorMsg, deviceIdentifier);
                } catch (APNSException e) {
                    String errorMsg = "Error occurred while sending a push notification message to APNS";
                    log.error(errorMsg, e);
                    operationUtils
                            .updateOperationWithErrorStatus(String.valueOf(operation.getId()), errorMsg, deviceIdentifier);
                } catch (PolicyManagementException e) {
                    String errorMsg = "Error occurred while enforcing policies";
                    log.error(errorMsg, e);
                    operationUtils
                            .updateOperationWithErrorStatus(apnsStatus.getCommandUUID(), errorMsg, deviceIdentifier);
                } catch (PolicyComplianceException e) {
                    String errorMsg = "Error occurred while checking policy compliance";
                    log.error(errorMsg, e);
                    operationUtils
                            .updateOperationWithErrorStatus(apnsStatus.getCommandUUID(), errorMsg, deviceIdentifier);
                } catch (OperationManagementException e) {
                    String errorMsg = "Issue in retrieving operation management service instance";
                    log.error(errorMsg, e);
                    operationUtils
                            .updateOperationWithErrorStatus(apnsStatus.getCommandUUID(), errorMsg, deviceIdentifier);
                } catch (ApplicationManagementException e) {
                    String errorMsg = "Issue in updating application list of device";
                    log.error(errorMsg, e);
                    operationUtils
                            .updateOperationWithErrorStatus(apnsStatus.getCommandUUID(), errorMsg, deviceIdentifier);
                } finally {
                    PrivilegedCarbonContext.endTenantFlow();
                }
            }
        } catch (SCEPException e) {
            String errorMsg = "Error occurred when retrieving tenanted device.";
            log.error(errorMsg, e);
            operationUtils.updateOperationWithErrorStatus(apnsStatus.getCommandUUID(), errorMsg, deviceIdentifier);
        } catch (KeystoreException e) {
            String errorMsg = "KeystoreException occurred when verifying signature";
            log.error(errorMsg, e);
            operationUtils.updateOperationWithErrorStatus(apnsStatus.getCommandUUID(), errorMsg, deviceIdentifier);
        } catch (PListException e) { //Removing error log as this exception is a result of bad enrollment and this
            // causes continuous error logs in prod servers.
            try {
                operationUtils.updateOperationWithErrorStatus(apnsStatus.getCommandUUID(), "Error occurred when" +
                        " extracting plist elements in check-in request.", deviceIdentifier);
            } catch (Exception e2) {
                log.error("Error when communicating with an iOS device. Device responded with the payload: " +
                        contentString);
            }
        } catch (IOSEnrollmentException e) {
            String errorMsg = "Error occurred while retrieving enrollment service.";
            log.error(errorMsg, e);
            operationUtils.updateOperationWithErrorStatus(apnsStatus.getCommandUUID(), errorMsg, deviceIdentifier);
        } catch (IOException e) {
            String errorMsg = "IOException occurred when copying input stream.";
            log.error(errorMsg, e);
            operationUtils.updateOperationWithErrorStatus(apnsStatus.getCommandUUID(), errorMsg, deviceIdentifier);
        } catch (DeviceManagementException e) {
            String errorMsg = "Error occurred when retrieving device from device management service";
            log.error(errorMsg, e);
            operationUtils.updateOperationWithErrorStatus(apnsStatus.getCommandUUID(), errorMsg, deviceIdentifier);
        }
        return Response.ok().build();
    }

    @GET
    @Path("/license")
    @Produces({MediaType.APPLICATION_JSON})
    @Consumes({MediaType.WILDCARD})
    public License getLicense() {
        License license = null;

        try {
            license =
                    IOSServiceUtils.getDeviceManagementService().getLicense(
                            DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_IOS,
                            DeviceManagementConstants.LanguageCodes.LANGUAGE_CODE_ENGLISH_US);
            if (license == null || license.getText() == null) {
                license.setName("ios");
                license.setLanguage("en_US");
                license.setVersion("1.0.0");
                license.setText("This End User License Agreement (\"Agreement\") is a legal agreement between you (\"You\") " +
                        "and WSO2, Inc., regarding the enrollment of Your personal mobile device (\"Device\") in SoR's " +
                        "mobile device management program, and the loading to and removal from Your Device and Your use " +
                        "of certain applications and any associated software and user documentation, whether provided in " +
                        "\"online\" or electronic format, used in connection with the operation of or provision of services " +
                        "to WSO2, Inc.,  BY SELECTING \"I ACCEPT\" DURING INSTALLATION, YOU ARE ENROLLING YOUR DEVICE, AND " +
                        "THEREBY AUTHORIZING SOR OR ITS AGENTS TO INSTALL, UPDATE AND REMOVE THE APPS FROM YOUR DEVICE AS " +
                        "DESCRIBED IN THIS AGREEMENT.  YOU ARE ALSO EXPLICITLY ACKNOWLEDGING AND AGREEING THAT (1) THIS IS " +
                        "A BINDING CONTRACT AND (2) YOU HAVE READ AND AGREE TO THE TERMS OF THIS AGREEMENT.\n" +
                        "\n" +
                        "IF YOU DO NOT ACCEPT THESE TERMS, DO NOT ENROLL YOUR DEVICE AND DO NOT PROCEED ANY FURTHER.\n" +
                        "\n" +
                        "You agree that: (1) You understand and agree to be bound by the terms and conditions contained " +
                        "in this Agreement, and (2) You are at least 21 years old and have the legal capacity to enter " +
                        "into this Agreement as defined by the laws of Your jurisdiction.  SoR shall have the right, " +
                        "without prior notice, to terminate or suspend (i) this Agreement, (ii) the enrollment of Your " +
                        "Device, or (iii) the functioning of the Apps in the event of a violation of this Agreement or " +
                        "the cessation of Your relationship with SoR (including termination of Your employment if You are " +
                        "an employee or expiration or termination of Your applicable franchise or supply agreement if You " +
                        "are a franchisee of or supplier to the WSO2 WSO2, Inc., system).  SoR expressly reserves all " +
                        "rights not expressly granted herein.");

            }

        } catch (DeviceManagementException e) {
            String msg = "Error occurred while retrieving the license configured for iOS device enrolment";
            log.error(msg, e);
        }

        return license;
    }
}
