diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/pom.xml b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/pom.xml new file mode 100644 index 0000000000..7dfdd93df4 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/pom.xml @@ -0,0 +1,192 @@ + + + + 4.0.0 + + + org.wso2.carbon.devicemgt + application-mgt + 3.0.46-SNAPSHOT + + + org.wso2.carbon.device.application.mgt.authhandler + 3.0.46-SNAPSHOT + war + WSO2 Carbon - Application Management Authentication Handler API + Proxy Service for Authentication Handling in WSO2 App Manager. + http://wso2.org + + + + + maven-war-plugin + + WEB-INF/lib/*cxf*.jar + auth#application-mgt#v1.0 + + + + + + + + deploy + + compile + + + org.apache.maven.plugins + maven-antrun-plugin + 1.7 + + + compile + + run + + + + + + + + + + + + + + + + + + client + + test + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + test + + java + + + + + + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + junit + junit + test + + + org.codehaus.jackson + jackson-jaxrs + + + org.codehaus.jackson + jackson-core-asl + + + javax.ws.rs + jsr311-api + provided + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + org.wso2.carbon + org.wso2.carbon.logging + provided + + + org.json.wso2 + json + + + commons-codec.wso2 + commons-codec + provided + + + + io.github.openfeign + feign-core + 9.5.0 + + + + io.github.openfeign + feign-jackson + 9.5.0 + + + + io.github.openfeign + feign-jaxrs + 9.5.0 + + + javax.servlet + servlet-api + provided + + + org.wso2.orbit.com.fasterxml.jackson.core + jackson-annotations + + + org.hibernate + hibernate-validator + + + javax.ws.rs + javax.ws.rs-api + + + diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/service/AuthHandlerService.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/service/AuthHandlerService.java new file mode 100644 index 0000000000..2f0a83228e --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/service/AuthHandlerService.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017, 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.application.mgt.auth.handler.service; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Path("/auth") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public interface AuthHandlerService { + + @POST + @Path("/login") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + Response login(@QueryParam("userName") String userName, @QueryParam("password") String password); + + @POST + @Path("/refresh") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + Response refresh(@QueryParam("refreshToken") String refreshToken, @QueryParam("clientId") String clientId, + @QueryParam("clientSecret") String clientSecret); + + @POST + @Path("/logout") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + Response logout(@QueryParam("token") String token, @QueryParam("clientId") String clientId, + @QueryParam("clientSecret") String clientSecret); +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/service/impl/AuthHandlerServiceImpl.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/service/impl/AuthHandlerServiceImpl.java new file mode 100644 index 0000000000..4c51e39ad5 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/service/impl/AuthHandlerServiceImpl.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2017, 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.application.mgt.auth.handler.service.impl; + +import feign.Client; +import feign.Feign; +import feign.auth.BasicAuthRequestInterceptor; +import feign.jackson.JacksonDecoder; +import feign.jackson.JacksonEncoder; +import feign.jaxrs.JAXRSContract; +import org.json.JSONObject; +import org.wso2.carbon.device.application.mgt.auth.handler.service.AuthHandlerService; +import org.wso2.carbon.device.application.mgt.auth.handler.util.Constants; +import org.wso2.carbon.device.application.mgt.auth.handler.util.dto.AccessTokenInfo; +import org.wso2.carbon.device.application.mgt.auth.handler.util.dto.ApiApplicationKey; +import org.wso2.carbon.device.application.mgt.auth.handler.util.dto.ApiApplicationRegistrationService; +import org.wso2.carbon.device.application.mgt.auth.handler.util.dto.ApiRegistrationProfile; +import org.wso2.carbon.device.application.mgt.auth.handler.util.dto.TokenIssuerService; +import org.wso2.carbon.device.application.mgt.auth.handler.util.dto.TokenRevokeService; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + +@Path("/auth") +public class AuthHandlerServiceImpl implements AuthHandlerService { + + private TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + } + }; + + private Client disableHostnameVerification = new Client.Default(getTrustedSSLSocketFactory(), + new HostnameVerifier() { + @Override + public boolean verify(String s, SSLSession sslSession) { + return true; + } + } + ); + + @POST + @Path("/login") + @Produces(MediaType.APPLICATION_JSON) + @Override + public Response login(@QueryParam("userName") String userName, @QueryParam("password") String password) { + + try { + ApiApplicationRegistrationService apiApplicationRegistrationService = Feign.builder() + .client(disableHostnameVerification) + .requestInterceptor(new BasicAuthRequestInterceptor(userName, password)) + .contract(new JAXRSContract()).encoder(new JacksonEncoder()).decoder(new JacksonDecoder()) + .target(ApiApplicationRegistrationService.class, Constants.API_APPLICATION_ENDPOINT); + ApiRegistrationProfile apiRegistrationProfile = new ApiRegistrationProfile(); + apiRegistrationProfile.setApplicationName(Constants.APPLICATION_NAME); + apiRegistrationProfile.setIsAllowedToAllDomains(false); + apiRegistrationProfile.setIsMappingAnExistingOAuthApp(false); + apiRegistrationProfile.setTags(Constants.TAGS); + ApiApplicationKey apiApplicationKey = apiApplicationRegistrationService.register(apiRegistrationProfile); + + //PasswordGrantType + TokenIssuerService tokenIssuerService = Feign.builder().client(disableHostnameVerification) + .requestInterceptor(new BasicAuthRequestInterceptor(apiApplicationKey.getConsumerKey(), + apiApplicationKey.getConsumerSecret())) + .contract(new JAXRSContract()).encoder(new JacksonEncoder()).decoder(new JacksonDecoder()) + .target(TokenIssuerService.class, Constants.TOKEN_ENDPOINT); + AccessTokenInfo accessTokenInfo = tokenIssuerService.getToken(Constants.PASSWORD_GRANT_TYPE, + userName, password, Constants.SCOPES); + JSONObject loginInfo = new JSONObject(accessTokenInfo); + loginInfo.append(Constants.USER_NAME, userName); + loginInfo.append(Constants.APPLICATION_INFO, new JSONObject(apiApplicationKey)); + System.out.println(loginInfo); + return Response.status(200).entity(loginInfo.toString()).build(); + } catch (Exception e) { + //return Response.status(500).build(); + } + return Response.status(200).build(); + } + + @POST + @Path("/refresh") + @Produces(MediaType.APPLICATION_JSON) + @Override + public Response refresh(@QueryParam("refreshToken") String refreshToken, @QueryParam("clientId") String clientId, + @QueryParam("clientSecret") String clientSecret) { + try { + TokenIssuerService tokenIssuerService = Feign.builder().client(disableHostnameVerification) + .requestInterceptor(new BasicAuthRequestInterceptor(clientId, clientSecret)) + .contract(new JAXRSContract()).encoder(new JacksonEncoder()).decoder(new JacksonDecoder()) + .target(TokenIssuerService.class, Constants.TOKEN_ENDPOINT); + AccessTokenInfo accessTokenInfo = tokenIssuerService.getRefreshToken(Constants.REFRESH_GRANT_TYPE, + refreshToken); + return Response.status(200).entity(new JSONObject(accessTokenInfo)).build(); + } catch (Exception e) { + return Response.status(500).build(); + } + } + + + @POST + @Path("/logout") + @Override + public Response logout(@QueryParam("token") String token, @QueryParam("clientId") String clientId, + @QueryParam("clientSecret") String clientSecret) { + try { + TokenRevokeService tokenRevokeService = Feign.builder().client(disableHostnameVerification) + .requestInterceptor(new BasicAuthRequestInterceptor(clientId, clientSecret)) + .contract(new JAXRSContract()).encoder(new JacksonEncoder()).decoder(new JacksonDecoder()) + .target(TokenRevokeService.class, Constants.TOKEN_ENDPOINT); + tokenRevokeService.revoke(token); + + return Response.status(200).build(); + } catch (Exception e) { + return Response.status(500).build(); + } + } + + private SSLSocketFactory getTrustedSSLSocketFactory() { + try { + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + return sc.getSocketFactory(); + } catch (KeyManagementException | NoSuchAlgorithmException e) { + return null; + } + } +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/Constants.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/Constants.java new file mode 100644 index 0000000000..5837fd2d68 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/Constants.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 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.application.mgt.auth.handler.util; + +//TODO: Remove hardcoded localhost and ports +public class Constants { + public static String SCOPES = "perm:application:get perm:application:create perm:application:update " + + "perm:application-mgt:login perm:application:delete perm:platform:add perm:platform:remove " + + "perm:roles:view perm:devices:view perm:platform:get"; + + public static String[] TAGS = {"device_management"}; + public static String USER_NAME = "userName"; + public static String APPLICATION_NAME = "applicationmgt_publisher"; + public static String TOKEN_ENDPOINT = "https://localhost:8243"; + public static String PASSWORD_GRANT_TYPE = "password"; + public static String REFRESH_GRANT_TYPE = "refresh_token"; + public static String API_APPLICATION_ENDPOINT = "https://localhost:9443/api-application-registration/"; + public static String APPLICATION_INFO = "application_info"; +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/AccessTokenInfo.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/AccessTokenInfo.java new file mode 100755 index 0000000000..3421aff3af --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/AccessTokenInfo.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017, 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.application.mgt.auth.handler.util.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * This hold access token info that returned from the api call + */ +@XmlRootElement(name = "AccessTokenInfo") +@JsonIgnoreProperties(ignoreUnknown = true) +public class AccessTokenInfo { + + @XmlElement(required = true, name = "tokenType") + private String tokenType; + + @XmlElement(required = true, name = "expiresIn") + private String expiresIn; + + @XmlElement(required = true, name = "refreshToken") + private String refreshToken; + + @XmlElement(required = true, name = "accessToken") + private String accessToken; + + public AccessTokenInfo() {} + + public String getTokenType() { + return tokenType; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + public String getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(String expiresIn) { + this.expiresIn = expiresIn; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + @Override + public String toString() { + return accessToken + " " + tokenType + " " + refreshToken + " "; + } +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/ApiApplicationKey.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/ApiApplicationKey.java new file mode 100644 index 0000000000..7a00854a89 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/ApiApplicationKey.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017, 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.application.mgt.auth.handler.util.dto; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * This holds api application consumer key and secret. + */ +@XmlRootElement +public class ApiApplicationKey { + @XmlElement + private String clientId; + @XmlElement + private String clientSecret; + + public String getConsumerKey() { + return this.clientId; + } + + public void setClientId(String consumerKey) { + this.clientId = consumerKey; + } + + public String getConsumerSecret() { + return this.clientSecret; + } + + public void setClientSecret(String consumerSecret) { + this.clientSecret = consumerSecret; + } +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/ApiApplicationRegistrationService.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/ApiApplicationRegistrationService.java new file mode 100755 index 0000000000..913e0e51c9 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/ApiApplicationRegistrationService.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017, 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.application.mgt.auth.handler.util.dto; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +/** + * This is the application registration service that exposed for apimApplicationRegistration + */ + +@Path("/register") +public interface ApiApplicationRegistrationService { + + /** + * This method is used to register api application + * + * @param registrationProfile contains the necessary attributes that are needed in order to register an app. + */ + @POST + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + ApiApplicationKey register(ApiRegistrationProfile registrationProfile); +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/ApiRegistrationProfile.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/ApiRegistrationProfile.java new file mode 100755 index 0000000000..cbe488dbb8 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/ApiRegistrationProfile.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017, 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.application.mgt.auth.handler.util.dto; + + +/** + * This class represents the data that are required to register + * the oauth application. + */ +public class ApiRegistrationProfile { + + public String applicationName; + public String tags[]; + public boolean isAllowedToAllDomains; + public String consumerKey; + public String consumerSecret; + public boolean isMappingAnExistingOAuthApp; + + public String getApplicationName() { + return applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + public String[] getTags() { + return tags; + } + + public void setTags(String[] tags) { + this.tags = tags; + } + + public boolean isAllowedToAllDomains() { + return isAllowedToAllDomains; + } + + public void setIsAllowedToAllDomains(boolean isAllowedToAllDomains) { + this.isAllowedToAllDomains = isAllowedToAllDomains; + } + + public boolean isMappingAnExistingOAuthApp() { + return isMappingAnExistingOAuthApp; + } + + public void setIsMappingAnExistingOAuthApp(boolean isMappingAnExistingOAuthApp) { + this.isMappingAnExistingOAuthApp = isMappingAnExistingOAuthApp; + } + + public String getConsumerKey() { + return consumerKey; + } + + public void setConsumerKey(String consumerKey) { + this.consumerKey = consumerKey; + } + + public String getConsumerSecret() { + return consumerSecret; + } + + public void setConsumerSecret(String consumerSecret) { + this.consumerSecret = consumerSecret; + } +} \ No newline at end of file diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/OAuthRequestInterceptor.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/OAuthRequestInterceptor.java new file mode 100755 index 0000000000..c99a738b7b --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/OAuthRequestInterceptor.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017, 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.application.mgt.auth.handler.util.dto; + + +import feign.RequestInterceptor; +import feign.RequestTemplate; + +import static feign.Util.checkNotNull; + +/** + * This is a request interceptor to add oauth token header. + */ +public class OAuthRequestInterceptor implements RequestInterceptor { + + private final String headerValue; + + /** + * Creates an interceptor that authenticates all requests with the specified OAUTH token + * + * @param token the access token to use for authentication + */ + public OAuthRequestInterceptor(String token) { + checkNotNull(token, "access_token"); + headerValue = "Bearer " + token; + } + @Override + public void apply(RequestTemplate template) { + template.header("Authorization", headerValue); + } +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/RegistrationProfile.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/RegistrationProfile.java new file mode 100755 index 0000000000..d189328099 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/RegistrationProfile.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017, 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.application.mgt.auth.handler.util.dto; + +/** + * This class represents the data that are required to register + * the oauth application. + */ +public class RegistrationProfile { + + public String callbackUrl; + public String clientName; + public String tokenScope; + public String owner; + public String grantType; + public String applicationType; + + private static final String TAG = RegistrationProfile.class.getSimpleName(); + + public String getCallbackUrl() { + return callbackUrl; + } + + public void setCallbackUrl(String callBackUrl) { + this.callbackUrl = callBackUrl; + } + + public String getClientName() { + return clientName; + } + + public void setClientName(String clientName) { + this.clientName = clientName; + } + + public String getTokenScope() { + return tokenScope; + } + + public void setTokenScope(String tokenScope) { + this.tokenScope = tokenScope; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public String getGrantType() { + return grantType; + } + + public void setGrantType(String grantType) { + this.grantType = grantType; + } + + public String getApplicationType() { + return applicationType; + } + + public void setApplicationType(String applicationType) { + this.applicationType = applicationType; + } + +} \ No newline at end of file diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/TokenIssuerService.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/TokenIssuerService.java new file mode 100755 index 0000000000..86eae76471 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/TokenIssuerService.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017, 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.application.mgt.auth.handler.util.dto; + +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +/** + * This hold the api definition that is used as a contract with netflix feign. + */ +@Path("/token") +public interface TokenIssuerService { + + @POST + @Produces(MediaType.APPLICATION_JSON) + AccessTokenInfo getToken(@QueryParam("grant_type") String grant, @QueryParam("username") String username, + @QueryParam("password") String password, @QueryParam("scope") String scope); + + @POST + @Produces(MediaType.APPLICATION_JSON) + AccessTokenInfo getRefreshToken(@QueryParam("grant_type") String grantType, + @QueryParam("refreshToken") String refreshToken); +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/Endpoints.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/TokenRevokeService.java similarity index 57% rename from components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/Endpoints.js rename to components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/TokenRevokeService.java index e3e76b69c2..331d24d333 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/Endpoints.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/java/org/wso2/carbon/device/application/mgt/auth/handler/util/dto/TokenRevokeService.java @@ -11,11 +11,25 @@ * 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 + * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ +package org.wso2.carbon.device.application.mgt.auth.handler.util.dto; + +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + /** - * Defines the list of App Manager APIs. - * */ \ No newline at end of file + * Api definition for token revoke that will be used as Feign contract. + * */ +@Path("/revoke") +public interface TokenRevokeService { + + @POST + Response revoke(@QueryParam("token")String accessToken); + +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/webapp/META-INF/permissions.xml b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/webapp/META-INF/permissions.xml new file mode 100644 index 0000000000..4fe224af46 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/webapp/META-INF/permissions.xml @@ -0,0 +1,35 @@ + + + + + + + + + + diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/webapp/META-INF/webapp-classloading.xml b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..ed2ed21624 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,35 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..64efa8991d --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/webapp/WEB-INF/web.xml b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..52559eead9 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.authhandler/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,115 @@ + + + + Application Management Auth Webapp + + JAX-WS/JAX-RS Application Management Endpoint + JAX-WS/JAX-RS Servlet + CXFServlet + + org.apache.cxf.transport.servlet.CXFServlet + + + + CXFServlet + /* + + + 60 + + + doAuthentication + false + + + + + managed-api-enabled + false + + + managed-api-owner + admin + + + isSharedWithAllTenants + true + + + + CorsFilter + org.apache.catalina.filters.CorsFilter + + cors.allowed.origins + * + + + cors.allowed.methods + GET,POST,DELETE,PUT + + + cors.allowed.headers + Content-Type + + + + + HttpHeaderSecurityFilter + org.apache.catalina.filters.HttpHeaderSecurityFilter + + hstsEnabled + false + + + + + ContentTypeBasedCachePreventionFilter + org.wso2.carbon.ui.filters.cache.ContentTypeBasedCachePreventionFilter + + patterns + text/html" ,application/json" ,text/plain + + + filterAction + enforce + + + httpHeaders + Cache-Control: no-store, no-cache, must-revalidate, private + + + + + HttpHeaderSecurityFilter + /* + + + + ContentTypeBasedCachePreventionFilter + /* + + + + CorsFilter + /* + + + \ No newline at end of file diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/pom.xml b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/pom.xml index a95ca6c8dd..962be6a3df 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/pom.xml +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/pom.xml @@ -19,15 +19,19 @@ - 4.0.0 - org.wso2.carbon.devicemgt application-mgt 3.0.46-SNAPSHOT + + 4.0.0 org.wso2.carbon.device.application.mgt.publisher.ui 3.0.46-SNAPSHOT + war + WSO2 Carbon - Application Management Publisher UI + WSO2 Carbon - Application Management Publisher UI React Application + http://wso2.org diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/package.json b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/package.json index 5e5fd515b5..445560ff20 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/package.json +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/package.json @@ -11,38 +11,40 @@ "dependencies": { "axios": "^0.16.2", "flux": "^3.1.3", - "history": "^4.6.3", + "history": "^4.7.2", "latest-version": "^3.1.0", - "material-ui": "^0.19.0", + "material-ui": "^0.19.1", "prop-types": "^15.5.10", "qs": "^6.5.0", "react": "^15.6.1", "react-dom": "^15.6.1", - "react-dropzone": "^4.1.0", + "react-dropzone": "^4.1.2", "react-images-uploader": "^1.1.0", - "react-material-ui-form-validator": "^0.5.0", - "react-modal": "^2.2.2", - "react-router": "^4.1.2", - "react-router-dom": "^4.1.2", + "react-material-ui-form-validator": "^0.5.1", + "react-modal": "^2.3.2", + "react-router": "^4.2.0", + "react-router-dom": "^4.2.2", "react-scripts": "1.0.10", "react-sliding-pane": "^1.2.3", "react-tap-event-plugin": "^2.0.1" }, "devDependencies": { - "babel-core": "^6.24.1", - "babel-loader": "^7.0.0", + "babel-core": "^6.26.0", + "babel-loader": "^7.1.2", "babel-plugin-transform-class-properties": "^6.24.1", "babel-preset-es2015": "^6.24.1", - "chai": "^4.0.2", "babel-preset-react": "^6.24.1", - "babel-register": "^6.24.1", - "css-loader": "^0.28.2", + "babel-register": "^6.26.0", + "chai": "^4.1.2", + "css-loader": "^0.28.7", "less": "^2.7.2", "less-loader": "^4.0.4", "mocha": "^3.4.1", "mock-local-storage": "^1.0.2", + "node-sass": "^4.5.3", + "sass-loader": "^6.0.6", "style-loader": "^0.18.1", - "webpack": "^2.5.0" + "webpack": "^2.7.0" }, "scripts": { "start": "react-scripts start", diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/public/themes/default/platform-create.css b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/public/themes/default/platform-create.css index 483cdedbff..36993707e2 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/public/themes/default/platform-create.css +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/public/themes/default/platform-create.css @@ -66,3 +66,8 @@ .createplatformdropzonep { margin: 70px 40px 70px 70px } + +.createPlatformTagWrapper { + display: flex; + flex-wrap: wrap; +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/App.css b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/App.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/App.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/App.jsx index 9dac4fa9c2..d045d345f9 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/App.jsx +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/App.jsx @@ -16,27 +16,27 @@ * under the License. */ -import './App.css'; +import './App.scss'; +import Theme from './theme'; import React, {Component} from 'react'; +import AuthHandler from './api/authHandler'; import createHistory from 'history/createBrowserHistory'; import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom' import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; import getMuiTheme from 'material-ui/styles/getMuiTheme'; import { - ApplicationCreate, - ApplicationListing, - BaseLayout, - Login, - NotFound, - PlatformCreate, - PlatformListing +ApplicationCreate, +ApplicationListing, +BaseLayout, +Login, +NotFound, +PlatformCreate, +PlatformListing } from './components'; -import Theme from './theme'; const history = createHistory({basename: '/publisher'}); - /** * This component defines the layout and the routes for the app. * All the content will be loaded inside the Base component. @@ -54,23 +54,40 @@ class Base extends Component { constructor() { super(); this.state = { - user: "admin" + user: null + } + } + + componentWillMount() { + let user = AuthHandler.getUser(); + if (user) { + if (!AuthHandler.isTokenExpired()) { + this.setState({user: user}); + } else { + console.log("expired!"); + this.setState({user: null}); + } } } + componentDidMount() { + + } + render() { - if (this.state.user) { + if (this.state.user !== null) { + console.log("Have User."); return (
- + - - + + @@ -80,8 +97,11 @@ class Base extends Component {
) + } else { + console.log("No user"); + return () } - return () + } } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/App.scss b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/App.scss new file mode 100644 index 0000000000..3f4bc89fef --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/App.scss @@ -0,0 +1,5 @@ +.middle-content { + width: 95%; + height: 100%; + margin: 1% 0 0 0; +} \ No newline at end of file diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/AuthHandler.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/AuthHandler.js deleted file mode 100644 index 9eb827eb70..0000000000 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/AuthHandler.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) 2017, 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. - */ diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/applicationMgtApi.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/applicationMgtApi.js new file mode 100644 index 0000000000..11d7d58e86 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/applicationMgtApi.js @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2017, 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. + */ +'use strict'; + +import Axios from 'axios'; +import AuthHandler from './authHandler'; +import Constants from '../common/constants'; +import Helper from './helpers/appMgtApiHelpers'; + +/** + * Api definitions related to application management. + * TODO: Work to be done on Application release. + * */ +export default class ApplicationMgtApi { + + /** + * Api for create an application. + * @param: applicationData: The application data object. This contains an object array of each step data from + * application creation wizard. + * + * From applicationData, the proper application object will be created and send it to the api. + * */ + static createApplication(applicationData) { + let {application, images} = Helper.buildApplication(applicationData); + const headers = AuthHandler.createAuthenticationHeaders("application/json"); + console.log(application); + console.log(images); + Axios.post(Constants.appManagerEndpoints.CREATE_APP, application, {headers: headers}); + } + + /** + * Upload the image artifacts (banner, icon, screenshots) related to the application. + * @param appId: The application uuid of the application which the images should be uploaded to. + * @param images: The images object. This contains icon, banner and screenshots. + * */ + static uploadImageArtifacts(appId, images) { + let formData = new FormData(); + formData.append('icon', images.icon); + formData.append('banner', images.banner); + formData.append('screenshot', images.screenshots); + console.log("Image", formData); + const headers = AuthHandler.createAuthenticationHeaders("multipart/form-data"); + return Axios.post(Constants.appManagerEndpoints.UPLOAD_IMAGE_ARTIFACTS + appId, formData, {headers: headers}); + } + + /** + * Method to handle application release process. + * */ + static releaseApplication(appId) { + + } + + /** + * Promote the current life cycle state of the application. + * @param appId: The uuid of the application which the state should be updated. + * @param nextState: The next lifecycle state that the application can be updated to. + * + * URL Pattern : /application/1.0/ + * */ + static updateLifeCycleState(appId, nextState) { + + } + + /** + * Get the next possible state, which the application can be promoted to. + * @param appId: The application uuid. + */ + static getNextLifeCycleState(appId) { + + } + + /** + * Edit created application. + * @param applicationData: The modified application data. + * */ + static editApplication(applicationData) { + let app = Helper.buildApplication(applicationData).application; + const headers = AuthHandler.createAuthenticationHeaders("application/json"); + return Axios.put(Constants.appManagerEndpoints.CREATE_APP, app, {headers: headers}); + } + + static getApplicationArtifacts(appId, artifactName) { + const headers = AuthHandler.createAuthenticationHeaders("image/png"); + return Axios.get(Constants.appManagerEndpoints.GET_IMAGE_ARTIFACTS + appId + "?name=" + artifactName, + {headers: headers}); + } + + static editApplicationArtifacts(appId, images) { + let formData = new FormData(); + formData.append('icon', images.icon); + formData.append('banner', images.banner); + formData.append('screenshot', images.screenshots); + const headers = AuthHandler.createAuthenticationHeaders("application/json"); + return Axios.put(Constants.appManagerEndpoints.UPLOAD_IMAGE_ARTIFACTS + appId, formData, {headers: headers}); + } + + /** + * Get all the created applications for the user. + * @return Object: The response object from the axios post. + * */ + static getApplications() { + const headers = AuthHandler.createAuthenticationHeaders("application/json"); + return Axios.get(Constants.appManagerEndpoints.GET_ALL_APPS, {headers: headers}); + } + + /** + * Get specific application. + * @param appId: The application Id. + * */ + static getApplication(appId) { + const headers = AuthHandler.createAuthenticationHeaders("application/json"); + return Axios.get(Constants.appManagerEndpoints.GET_ALL_APPS + appId, {headers: headers}); + } + + /** + * Delete specified application. + * @param appId: The id of the application which is to be deleted. + * */ + static deleteApplication(appId) { + const headers = AuthHandler.createAuthenticationHeaders("application/json"); + return Axios.delete(Constants.appManagerEndpoints.GET_ALL_APPS + appId, {headers: headers}); + } +} \ No newline at end of file diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/authHandler.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/authHandler.js new file mode 100644 index 0000000000..111027a2dc --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/authHandler.js @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2017, 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. + */ +'use strict'; + +import Axios from 'axios'; +import User from './data/user'; +import Utils from './data/utils'; +import Constants from "../common/constants"; + +/** + * Handles all tasks related to Authentication and Authorization. + * Generate access tokens, verify the user has necessary permissions etc. + * */ +class AuthHandler { + + /** + * Sends a request to the auth handler endpoint (auth/application-mgt/v1.0/auth/login) and generate token pair. + * @param userName: The user name of the user. + * @param password: The user password. + * @return Object: The response object from the axios post. + * */ + static login(userName, password) { + const headers = {"Content-type": "application/json"}; + let login_promise = + Axios.post(Constants.userConstants.LOGIN_URL+"?userName=" + userName+ "&password=" + password, + null, {headers: headers}); + + login_promise.then(response => { + console.log(response); + const userName = response.data.userName; + const validityPeriod = response.data.expiresIn; // In seconds + const WSO2_IOT_TOKEN = response.data.accessToken; + const refreshToken = response.data.refreshToken; + const clientId = response.data.application_info[0].consumerKey; + const clientSecret = response.data.application_info[0].consumerSecret; + + const user = new User(userName, clientId, clientSecret, validityPeriod); + console.log(user); + user.setAuthToken(WSO2_IOT_TOKEN, validityPeriod); + let expiresIn = Date.now() + (validityPeriod * 1000); + localStorage.setItem("expiresIn", expiresIn); + AuthHandler.setUser(user); + } + ); + return login_promise; + }; + + /** + * Persists the user object in browser's local storage. + * @param user: The user object. + * */ + static setUser(user) { + if (!user instanceof User) { + throw "Invalid user object"; + } + user.created = Date.now(); + localStorage.setItem(Constants.userConstants.WSO2_USER, JSON.stringify(user.toJson())); + /* TODO: IMHO it's better to get this key (`wso2_user`) from configs */ + } + + static unauthorizedErrorHandler(error_response) { + if (error_response.status !== 401) { /* Skip unrelated response code to handle in unauthorizedErrorHandler*/ + throw error_response; + /* re throwing the error since we don't handle it here and propagate to downstream error handlers in catch chain*/ + } + let message = "The session has expired" + ".
You will be redirect to the login page ..."; + if (true) { + alert(message); + } else { + throw error_response; + } + } + + /** + * Get the logged in user. + * @return User: The logged in user object. + * */ + static getUser() { + const userData = localStorage.getItem(Constants.userConstants.WSO2_USER); + const partialToken = Utils.getCookie(Constants.userConstants.PARTIAL_TOKEN); + + if (!(userData && partialToken)) { + return null; + } + return User.fromJson(JSON.parse(userData)); + } + + isLoggedIn() { + + } + + static logout() { + const user = AuthHandler.getUser(); + const clientId = user.getClientId(); + const clientSecret = user.getClientSecret(); + const token = user.getAuthToken(); + const headers = {"Content-type": "application/json"}; + + let login_promise = Axios.post(Constants.userConstants.LOGOUT_URL+"?token=" + token + "&clientId=" + clientId + + "&clientSecret=" + clientSecret, + null, {headers: headers}); + login_promise.then( + (response) => { + Utils.delete_cookie(Constants.userConstants.PARTIAL_TOKEN); + localStorage.removeItem(Constants.userConstants.WSO2_USER); + window.location = "/"; + } + ).catch( + (err) => { + AuthHandler.unauthorizedErrorHandler(err); + } + ) + } + + /** + * Checks whether the access token is expired. + * @return boolean: True if expired. False otherwise. + * */ + static isTokenExpired() { + const expiresIn = localStorage.getItem("expiresIn"); + return (expiresIn < Date.now()); + } + + static createAuthenticationHeaders(contentType) { + return { + "Authorization": "Bearer " + AuthHandler.getUser().getAuthToken(), + "Content-Type": contentType, + }; + + }; +} + +export default AuthHandler; diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/data/user.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/data/user.js new file mode 100644 index 0000000000..dd6e79b917 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/data/user.js @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2017, 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. + */ +"use strict"; + +import Utils from './utils' +import Constants from '../../common/constants'; +/** + * Represent an user logged in to the application, There will be allays one user per session and + * this user details will be persist in browser localstorage. + */ +export default class User { + constructor(name, clientId, clientSecret, validityPeriod) { + if (User._instance) { + return User._instance; + } + + this._userName = name; + this._clientId = clientId; + this._clientSecret = clientSecret; + this._expires = validityPeriod; + User._instance = this; + } + + /** + * OAuth scopes which are available for use by this user + * @returns {Array} : An array of scopes + */ + get scopes() { + return this._scopes; + } + + /** + * Set OAuth scopes available to be used by this user + * @param {Array} newScopes : An array of scopes + */ + set scopes(newScopes) { + Object.assign(this.scopes, newScopes); + } + + /** + * Get the JS accessible access token fragment from cookie storage. + * @returns {String|null} + */ + getAuthToken() { + return Utils.getCookie(Constants.userConstants.PARTIAL_TOKEN); + } + + getClientId() { + return this._clientId; + } + + getClientSecret() { + return this._clientSecret; + } + + /** + * Store the JavaScript accessible access token segment in cookie storage + * @param {String} newToken : Part of the access token which needs when accessing REST API + * @param {Number} validityPeriod : Validity period of the cookie in seconds + */ + setAuthToken(newToken, validityPeriod) { + Utils.delete_cookie(Constants.userConstants.PARTIAL_TOKEN); + Utils.setCookie(Constants.userConstants.PARTIAL_TOKEN, newToken, validityPeriod); + } + + /** + * Get the user name of logged in user. + * @return String: User name + * */ + getUserName() { + return this._userName; + } + + /** + * Provide user data in JSON structure. + * @returns {JSON} : JSON representation of the user object + */ + toJson() { + return { + name: this._userName, + clientId: this._clientId, + clientSecret: this._clientSecret, + expires: this._expires + }; + } + + /** + * User utility method to create an user from JSON object. + * @param {JSON} userJson : Need to provide user information in JSON structure to create an user object + * @returns {User} : An instance of User(this) class. + */ + static fromJson(userJson) { + const _user = new User(userJson.name); + _user._clientId = userJson.clientId; + _user._clientSecret = userJson.clientSecret; + _user._expires = userJson.expires; + + console.log(_user); + return _user; + } +} + +User._instance = null; // A private class variable to preserve the single instance of a swaggerClient diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/data/utils.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/data/utils.js new file mode 100644 index 0000000000..815c05b796 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/data/utils.js @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2017, 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. + */ + +/** + * Utility class for Publisher application + */ +class PublisherUtils { + + /** + * TODO: Remove this method one the initial phase is done, This is used to continue the API class until the login page is create + * @returns {promise} + */ + // static autoLogin() { + // let auth = new AuthManager(); + // return auth.authenticateUser('admin', 'admin'); + // } + + /** + * Get JavaScript accessible cookies saved in browser, by giving the cooke name. + * @param {String} name : Name of the cookie which need to be retrived + * @returns {String|null} : If found a cookie with given name , return its value,Else null value is returned + */ + static getCookie(name) { + let pairs = document.cookie.split(";"); + let cookie = null; + for (let pair of pairs) { + pair = pair.split("="); + let cookie_name = pair[0].trim(); + let value = encodeURIComponent(pair[1]); + if (cookie_name === name) { + cookie = value; + break; + } + } + return cookie; + } + + /** + * Delete a browser cookie given its name + * @param {String} name : Name of the cookie which need to be deleted + */ + static delete_cookie(name) { + document.cookie = name + '=; Path=' + "/" + '; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + } + + /** + * Set a cookie with given name and value assigned to it. Cookies can be only set to the same origin, + * which the script is running + * @param {String} name : Name of the cookie which need to be set + * @param {String} value : Value of the cookie, expect it to be URLEncoded + * @param {number} validityPeriod : (Optional) Validity period of the cookie in seconds + * @param {String} path : Path which needs to set the given cookie + * @param {boolean} secured : secured parameter is set + */ + static setCookie(name, value, validityPeriod, path = "/", secured = true) { + let expires = ""; + const securedDirective = secured ? "; Secure" : ""; + if (validityPeriod) { + const date = new Date(); + date.setTime(date.getTime() + validityPeriod * 1000); + expires = "; expires=" + date.toUTCString(); + } + + document.cookie = name + "=" + value + expires + "; path=" + path + securedDirective + validityPeriod + } + + /** + * Given an object returns whether the object is empty or not + * @param {Object} object : Any JSON object + * @returns {boolean} + */ + static isEmptyObject(object) { + return Object.keys(object).length === 0 && object.constructor === Object + } +} + +export default PublisherUtils; diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/helpers/appMgtApiHelpers.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/helpers/appMgtApiHelpers.js new file mode 100644 index 0000000000..acf3d27815 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/helpers/appMgtApiHelpers.js @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017, 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. + */ + +'use strict'; + +/** + * Helper methods for app publisher. + * */ +export default class Helper { + + /** + * Generate application object from form data passed. + * @param appData: Application data from the application creation form. + * @return {Object, Object}: The application object and the set of images related to the application. + * */ + static buildApplication(appData) { + + let application = {}; + let images = {}; + + for (let step in appData) { + let tmpData = appData[step].data.step; + for (let prop in tmpData) { + if (prop === 'banner' || prop === 'screenshots' || prop === 'icon') { + images[prop] = tmpData[prop]; + } else if(prop === 'tags') { + application[prop] = Helper.stringifyTags(tmpData[prop]); + } else { + application[prop] = tmpData[prop]; + } + } + } + return {application, images}; + } + + static stringifyTags(tags) { + let tmpTags = []; + for (let tag in tags) { + console.log(tag); + tmpTags.push(tags[tag].value); + } + + return tmpTags; + } + +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/platformMgtApi.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/platformMgtApi.js new file mode 100644 index 0000000000..709343b3d3 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/api/platformMgtApi.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017, 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. + */ +'use strict'; + +import Axios from 'axios'; +import AuthHandler from './authHandler'; +import Constants from '../common/constants'; + +/** + * Api definitions for Platform management. + * */ +export default class PlatformMgtApi{ + /** + * Create a new Platform + * @param platformData: The platform data object. + * */ + static createPlatform(platformData) { + const headers = AuthHandler.createAuthenticationHeaders("application/json"); + Axios.post(Constants.platformManagerEndpoints.CREATE_PLATFORM, platformData, {headers: headers}).then( + function (response) { + console.log(response); + } + ).catch(function (err) { + console.log(err); + }); + } + + /** + * Get available platforms + * */ + static getPlatforms() { + const headers = AuthHandler.createAuthenticationHeaders("application/json"); + return Axios.get(Constants.platformManagerEndpoints.GET_ENABLED_PLATFORMS, {headers: headers}); + } + + /** + * Get the user specified platform + * @param platformId: The identifier of the platform + * */ + static getPlatform(platformId) { + const headers = AuthHandler.createAuthenticationHeaders("application/json"); + return Axios.get(Constants.platformManagerEndpoints.GET_PLATFORM + platformId, {headers: headers}); + } + + /** + * Delete specified platform + * @param platformId: The id of the platform which is to be deleted. + * */ + static deletePlatform(platformId) { + const headers = AuthHandler.createAuthenticationHeaders("application/json"); + return Axios.delete(Constants.platformManagerEndpoints.GET_PLATFORM + platformId, {headers: headers}); + } +} \ No newline at end of file diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/common/constants.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/common/constants.js index 9eb827eb70..336b122e7b 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/common/constants.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/common/constants.js @@ -15,3 +15,33 @@ * specific language governing permissions and limitations * under the License. */ + +'use strict'; + +//TODO: Replace the server address with response from auth endpoint and remove hardcoded ids etc. +export default class Constants { + + static scopes = 'perm:application:get perm:application:create perm:application:update perm:application-mgt:login' + + ' perm:application:delete perm:platform:add perm:platform:remove perm:roles:view perm:devices:view'; + + static appManagerEndpoints = { + GET_ALL_APPS: 'https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/', + CREATE_APP: 'https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/', + UPLOAD_IMAGE_ARTIFACTS: 'https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/upload-image-artifacts/', //+appId + GET_IMAGE_ARTIFACTS: "https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/image-artifacts/" + }; + + static platformManagerEndpoints = { + CREATE_PLATFORM: 'https://localhost:8243/api/application-mgt/v1.0/platforms/1.0.0', + GET_ENABLED_PLATFORMS: 'https://localhost:8243/api/application-mgt/v1.0/platforms/1.0.0?status=ENABLED', + GET_PLATFORM: 'https://localhost:8243/api/application-mgt/v1.0/platforms/1.0.0/' + }; + + static userConstants = { + LOGIN_URL:"https://localhost:9443/auth/application-mgt/v1.0/auth/login", + LOGOUT_URL: "https://localhost:9443/auth/application-mgt/v1.0/auth/logout", + REFRESH_TOKEN_URL: "", + WSO2_USER: 'wso2_user', + PARTIAL_TOKEN: 'WSO2_IOT_TOKEN' + } +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/ApplicationCreate.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/ApplicationCreate.jsx index 870ed0cdb9..49cd06abf6 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/ApplicationCreate.jsx +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/ApplicationCreate.jsx @@ -16,15 +16,16 @@ * under the License. */ +import Theme from '../../theme'; import React, {Component} from 'react'; import Dialog from 'material-ui/Dialog'; import {withRouter} from 'react-router-dom'; import FlatButton from 'material-ui/FlatButton'; import {Step1, Step2, Step3} from './CreateSteps'; import RaisedButton from 'material-ui/RaisedButton'; +import ApplicationMgtApi from '../../api/applicationMgtApi'; import {Card, CardActions, CardTitle} from 'material-ui/Card'; import {Step, StepLabel, Stepper,} from 'material-ui/Stepper'; -import Theme from '../../theme'; /** * The App Create Component. @@ -38,12 +39,14 @@ class ApplicationCreate extends Component { constructor() { super(); this.scriptId = "application-create"; - this.setStepData.bind(this); - this.removeStepData.bind(this); - this.handleSubmit.bind(this); - this.handleCancel.bind(this); - this.handleYes.bind(this); - this.handleNo.bind(this); + this.setStepData = this.setStepData.bind(this); + this.removeStepData = this.removeStepData.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.handleCancel = this.handleCancel.bind(this); + this.handleYes = this.handleYes.bind(this); + this.handleNo = this.handleNo.bind(this); + this.handlePrev = this.handlePrev.bind(this); + this.handleNext = this.handleNext.bind(this); this.state = { finished: false, stepIndex: 0, @@ -66,7 +69,7 @@ class ApplicationCreate extends Component { /** * Handles next button click event. * */ - handleNext = () => { + handleNext() { console.log("Handle Next"); const {stepIndex} = this.state; this.setState({ @@ -78,15 +81,24 @@ class ApplicationCreate extends Component { /** * Handles form submit. * */ - handleSubmit = () => { - console.log(this.state.stepData); + handleSubmit() { + let stepData = this.state.stepData; + let applicationCreationPromise = ApplicationMgtApi.createApplication(stepData); + applicationCreationPromise.then( response => { + this.handleYes(); + } + ).catch( + function (err) { + console.log(err); + } + ); }; /** * Handles cancel button click event. * This will show a confirmation dialog to cancel the application creation process. * */ - handleCancel = () => { + handleCancel() { this.setState({isDialogOpen: true}); }; @@ -94,7 +106,7 @@ class ApplicationCreate extends Component { * Handled [ < Prev ] button click. * This clears the data in the current step and returns to the previous step. * */ - handlePrev = () => { + handlePrev() { const {stepIndex} = this.state; if (stepIndex > 0) { this.removeStepData(); @@ -104,8 +116,10 @@ class ApplicationCreate extends Component { /** * Saves form data in each step in to the state. + * @param step: The step number of the step data. + * @param data: The form data of the step. * */ - setStepData = (step, data) => { + setStepData(step, data) { console.log(step, data, this.state.stepData); let tmpStepData = this.state.stepData; tmpStepData.push({step: step, data: data}); @@ -116,7 +130,7 @@ class ApplicationCreate extends Component { /** * Remove the last data point * */ - removeStepData = () => { + removeStepData() { let tempData = this.state.stepData; tempData.pop(); this.setState({stepData: tempData}); @@ -126,7 +140,7 @@ class ApplicationCreate extends Component { * Handles the Yes button in app creation cancellation dialog. * Clears all the form data and reset the wizard. * */ - handleYes = () => { + handleYes() { this.setState({finished: false, stepIndex: 0, stepData: [], isDialogOpen: false}); }; @@ -134,7 +148,7 @@ class ApplicationCreate extends Component { * Handles No button in app creation cancellation dialog. * Returns to the same step. * */ - handleNo = () => { + handleNo() { this.setState({isDialogOpen: false}); }; @@ -150,28 +164,38 @@ class ApplicationCreate extends Component { getStepContent(stepIndex) { switch (stepIndex) { case 0: - return ; + return ( + + ); case 1: - return ; + return ( + + ); case 2: - return ; + return ( + + ); default: - return 'You\'re a long way from home sonny jim!'; + return
; } } - render() { const {finished, stepIndex} = this.state; - const contentStyle = {margin: '0 16px'}; /** * Defines the dialog box actions. [Yes][No] @@ -189,7 +213,6 @@ class ApplicationCreate extends Component { />, ]; - return (
diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/ApplicationListing.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/ApplicationListing.jsx index fcceb20ca5..15b3fd0680 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/ApplicationListing.jsx +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/ApplicationListing.jsx @@ -16,12 +16,14 @@ * under the License. */ +import Theme from '../../theme'; import React, {Component} from 'react'; import {withRouter} from 'react-router-dom'; import TextField from 'material-ui/TextField'; +import AuthHandler from "../../api/authHandler"; import DataTable from '../UIComponents/DataTable'; +import ApplicationMgtApi from '../../api/applicationMgtApi'; import {Card, CardActions, CardTitle} from 'material-ui/Card'; -import Theme from '../../theme'; /** * The App Create Component. @@ -34,69 +36,39 @@ import Theme from '../../theme'; class ApplicationListing extends Component { constructor() { super(); + this.searchApplications = this.searchApplications.bind(this); + this.onRowClick = this.onRowClick.bind(this); + this.setData = this.setData.bind(this); + this.sortData = this.sortData.bind(this); + this.compare = this.compare.bind(this); this.state = { - data: [], + searchedApplications: [], + applications: [], asc: true }; this.scriptId = "application-listing"; } - data = [ - { - id: Math.random(), - applicationName:"Cne", - platform:'Android', - category:"Public", - status: "Created" - }, - { - id: Math.random(), - applicationName:"Gone", - platform:'IOS', - category:"Public", - status: "Created" - }, - { - id: Math.random(), - applicationName:"Ane", - platform:'Android', - category:"Public", - status: "Created" - }, - { - id: Math.random(), - applicationName:"one", - platform:'Android', - category:"Public", - status: "Created" - }, - { - id: Math.random(), - applicationName:"one", - platform:'Android', - category:"Public", - status: "Created" - }, - ]; - headers = [ { data_id: "image", data_type: "image", sortable: false, - label: ""}, + label: "" + }, { data_id: "applicationName", data_type: "string", sortable: true, label: "Application Name", - sort: this._sortData.bind(this) + sort: this.sortData }, { data_id: "platform", data_type: "image_array", sortable: false, - label: "Platform"}, + label: "Platform" + }, { data_id: "category", data_type: "string", @@ -112,8 +84,6 @@ class ApplicationListing extends Component { ]; componentWillMount() { - //Fetch all the applications from backend and create application objects. - this.setState({data: this.data}); /** *Loading the theme files based on the the user-preference. @@ -123,38 +93,70 @@ class ApplicationListing extends Component { componentWillUnmount() { Theme.removeThemingScripts(this.scriptId); + // this.setState({data: this.data}); } + componentDidMount() { + let getApps = ApplicationMgtApi.getApplications(); + getApps.then(response => { + let apps = this.setData(response.data.applications); + console.log(apps); + this.setState({searchedApplications: apps}); + // console.log(this.setState({data: response.data}), console.log(this.state)); + }).catch(err => { + AuthHandler.unauthorizedErrorHandler(err); + }); + } + + /** + * Extract application from application list and update the state. + * */ + setData(applications) { + let apps = []; + for (let app in applications) { + let application = {}; + application.id = applications[app].uuid; + application.applicationName = applications[app].name; + application.platform = applications[app].platform.name; + application.category = applications[app].category.id; + application.status = applications[app].currentLifecycle.lifecycleState.name; + apps.push(application); + } + + this.setState({searchedApplications: apps}); + } /** * Handles the search action. * When typing in the search bar, this method will be invoked. + * @param event: The event triggered from typing in the search box. + * @param searchText: The text that typed in the search box. * */ - _searchApplications(event, word) { + searchApplications(event, searchText) { let searchedData; - if (word){ - searchedData = this.data.filter((dataItem) => { - return dataItem.applicationName.includes(word); - }); - } else { - searchedData = this.data; - } - - this.setState({data: searchedData}, console.log("Searched data ", this.state.data)); + if (searchText) { + searchedData = this.state.applications.filter((dataItem) => { + return dataItem.applicationName.includes(searchText); + }); + } else { + searchedData = this.state.applications; + } + this.setState({searchedApplications: searchedData}, console.log("Searched data ", this.state.searchedApplications)); } /** * Handles sort data function and toggles the asc state. * asc: true : sort in ascending order. * */ - _sortData() { + sortData() { + console.log(this.state); let isAsc = this.state.asc; - let datas = isAsc?this.data.sort(this._compare):this.data.reverse(); - this.setState({data: datas, asc: !isAsc}); + let sortedData = isAsc ? this.state.searchedApplications.sort(this.compare) : this.data.reverse(); + this.setState({searchedApplications: sortedData, asc: !isAsc}); } - _compare(a, b) { + compare(a, b) { if (a.applicationName < b.applicationName) return -1; if (a.applicationName > b.applicationName) @@ -162,27 +164,33 @@ class ApplicationListing extends Component { return 0; } - _onRowClick(id) { - this.props.history.push("apps/"+id); + onRowClick(id) { + ApplicationMgtApi.getApplication(id).then(response => { + console.log(response); + }).catch(err => { + console.log(err) + }); + // this.props.history.push("apps/" + id); } render() { return (
- + - - - - + - -
); +
+ ); } } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/CreateSteps/Step1.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/CreateSteps/Step1.jsx index f414a8c278..87ac4875dc 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/CreateSteps/Step1.jsx +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/CreateSteps/Step1.jsx @@ -16,12 +16,14 @@ * under the License. */ +import Theme from '../../../theme'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; import MenuItem from 'material-ui/MenuItem'; import SelectField from 'material-ui/SelectField'; +import AuthHandler from "../../../api/authHandler"; import RaisedButton from 'material-ui/RaisedButton'; -import Theme from '../../../theme'; +import PlatformMgtApi from "../../../api/platformMgtApi"; /** * The first step of the application creation wizard. @@ -39,11 +41,15 @@ import Theme from '../../../theme'; class Step1 extends Component { constructor() { super(); + this.setPlatforms = this.setPlatforms.bind(this); + this.platforms = []; this.state = { finished: false, stepIndex: 0, store: 1, - platform: 1, + platformSelectedIndex: 0, + platform: "", + platforms: [], stepData: [], title: "", titleError: "" @@ -61,22 +67,40 @@ class Step1 extends Component { componentWillUnmount() { Theme.removeThemingScripts(this.scriptId); } + componentDidMount() { + //Get the list of available platforms and set to the state. + PlatformMgtApi.getPlatforms().then(response => { + console.log(response); + this.setPlatforms(response.data); + }).catch(err => { + AuthHandler.unauthorizedErrorHandler(err); + }) + } /** - * Invokes the handleNext function in Create component. + * Extract the platforms from the response data and populate the state. + * @param platforms: The array returned as the response. * */ - _handleNext = () => { - this.props.handleNext(); - }; + setPlatforms(platforms) { + let tmpPlatforms = []; + for (let index in platforms) { + let platform = {}; + platform = platforms[index]; + tmpPlatforms.push(platform); + } + this.setState({platforms: tmpPlatforms, platformSelectedIndex: 0, platform: tmpPlatforms[0].identifier}) + } /** * Persist the current form data to the state. * */ - _setStepData() { - var step = { + setStepData() { + console.log("Platforms",this.state.platforms); + let step = { store: this.state.store, - platform: this.state.platform + platform: this.state.platforms[this.state.platformSelectedIndex] }; + console.log(step); this.props.setData("step1", {step: step}); } @@ -86,32 +110,25 @@ class Step1 extends Component { * Sets the data to the state. * Invokes the handleNext method of Create component. * */ - _handleClick() { - this._setStepData(); + handleClick() { + this.setStepData(); } /** * Triggers when changing the Platform selection. * */ - _onChangePlatform = (event, index, value) => { - console.log(value); - this.setState({platform: value}); + onChangePlatform(event, index, value) { + console.log(this.state.platforms[index]); + this.setState({platform: this.state.platforms[index].identifier, platformSelectedIndex: index}); }; /** * Triggers when changing the Store selection. * */ - _onChangeStore = (event, index, value) => { + onChangeStore(event, index, value) { this.setState({store: value}); }; - /** - * Triggers when user types on Title text field. - * */ - _onChangeTitle = (event, value) => { - this.setState({title: value}); - }; - render() { return (
@@ -122,30 +139,38 @@ class Step1 extends Component { floatingLabelText="Store Type*" value={this.state.store} floatingLabelFixed={true} - onChange={this._onChangeStore.bind(this)} + onChange={this.onChangeStore.bind(this)} > - - -
+ + + +
- - - + {this.state.platforms.length > 0 ? this.state.platforms.map(platform => { + return ( + + ) + }) :
} + +
-

diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/CreateSteps/Step2.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/CreateSteps/Step2.jsx index 198055a092..fbfc9b8f57 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/CreateSteps/Step2.jsx +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/CreateSteps/Step2.jsx @@ -17,6 +17,7 @@ */ import PropTypes from 'prop-types'; +import Theme from '../../../theme'; import Chip from 'material-ui/Chip'; import Dropzone from 'react-dropzone'; import React, {Component} from 'react'; @@ -28,7 +29,6 @@ import SelectField from 'material-ui/SelectField'; import RaisedButton from 'material-ui/RaisedButton'; import Clear from 'material-ui/svg-icons/content/clear'; import {GridList, GridTile} from 'material-ui/GridList'; -import Theme from '../../../theme'; /** * The Second step of application create wizard. @@ -56,16 +56,17 @@ class Step2 extends Component { super(); this.state = { tags: [], + icon: [], + title: "", + errors: {}, + banner: [], defValue: "", category: 0, visibility: 0, - errors: {}, - title: "", - shortDescription: "", description: "", - banner: [], screenshots: [], - icon: [] + identifier: "", + shortDescription: "" }; this.scriptId = "application-create-step2"; } @@ -86,19 +87,19 @@ class Step2 extends Component { * Clears the tags text field. * Chip gets two parameters: Key and value. * */ - _addTags(event) { + addTags(event) { let tags = this.state.tags; if (event.charCode === 13) { event.preventDefault(); tags.push({key: Math.floor(Math.random() * 1000), value: event.target.value}); - this.setState({tags, defValue: ""}); + this.setState({tags, defValue: ""}, console.log(tags)); } } /** * Set the value for tag. * */ - _handleTagChange(event) { + handleTagChange(event) { let defaultValue = this.state.defValue; defaultValue = event.target.value; this.setState({defValue: defaultValue}) @@ -107,21 +108,21 @@ class Step2 extends Component { /** * Invokes the handleNext function in Create component. * */ - _handleNext() { + handleNext() { let fields = [{name: "Title", value: this.state.title}, {name: "Short Description", value: this.state.shortDescription}, {name: "Description", value: this.state.description}, {name: "Banner", value: this.state.banner}, {name: "Screenshots", value: this.state.screenshots}, + {name: "Identifier", value: this.state.identifier}, {name: "Icon", value: this.state.icon}]; - this._validate(fields); - // this.props.handleNext(); + this.validate(fields); } /** * Invokes the handlePrev function in Create component. * */ - _handlePrev() { + handlePrev() { this.props.handlePrev(); } @@ -129,7 +130,7 @@ class Step2 extends Component { * Handles Chip delete function. * Removes the tag from state.tags * */ - _handleRequestDelete = (key) => { + handleRequestDelete(key) { this.chipData = this.state.tags; const chipToDelete = this.chipData.map((chip) => chip.key).indexOf(key); this.chipData.splice(chipToDelete, 1); @@ -139,18 +140,18 @@ class Step2 extends Component { /** * Creates Chip array from state.tags. * */ - _renderChip(data) { + renderChip(data) { return ( this._handleRequestDelete(data.key)} + onRequestDelete={() => this.handleRequestDelete(data.key)} className="applicationCreateChip"> {data.value} ); } - _onVisibilitySelect = (event, index, value) => { + onVisibilitySelect(event, index, value) { console.log(value); let comp = @@ -167,7 +168,7 @@ class Step2 extends Component { /** * Validate the form. * */ - _validate(fields) { + validate(fields) { let errors = {}; let errorsPresent = false; fields.forEach(function (field) { @@ -181,6 +182,15 @@ class Step2 extends Component { } break; } + case 'Identifier': { + if (field.value === "") { + errors[field.name] = field.name + " is required!"; + errorsPresent = true; + } else { + errorsPresent = false; + } + break; + } case 'Short Description': { if (field.value === "") { errors[field.name] = field.name + " is required!"; @@ -219,7 +229,7 @@ class Step2 extends Component { } case 'Screenshots': { if (field.value.length < 3) { - errors[field.name] = "3 " +field.name + " are required!"; + errors[field.name] = "3 " + field.name + " are required!"; errorsPresent = true; } else { errorsPresent = false; @@ -229,40 +239,40 @@ class Step2 extends Component { } }); - console.log(errorsPresent); if (!errorsPresent) { - this._setStepData(); + this.setStepData(); } else { this.setState({errors: errors}, console.log(errors)); } - } /** * Creates an object with the current step data and persist in the parent. * */ - _setStepData() { + setStepData() { let stepData = { - title: this.state.title, - description: this.state.description, - shortDescription: this.state.shortDescription, + icon: this.state.icon, + name: this.state.name, tags: this.state.tags, banner: this.state.banner, + category: this.categories[this.state.category], + identifier: this.state.identifier, screenshots: this.state.screenshots, - icon: this.state.icon + description: this.state.description, + shortDescription: this.state.shortDescription }; this.props.setData("step2", {step: stepData}); - } + }; /** * Set text field values to state. * */ - _onTextFieldChange(event, value) { + onTextFieldChange(event, value) { let field = event.target.id; switch (field) { - case "title": { - this.setState({title: value}); + case "name": { + this.setState({name: value}); break; } case "shortDescription": { @@ -273,30 +283,34 @@ class Step2 extends Component { this.setState({description: value}); break; } + case "identifier": { + this.setState({identifier: value}); + break; + } } - } + }; /** * Removed user uploaded banner. * */ - _removeBanner(event, d) { + removeBanner(event, d) { console.log(event, d); this.setState({banner: []}); - } + }; /** * Removes uploaded icon. * */ - _removeIcon(event) { + removeIcon(event) { this.setState({icon: []}); - } + }; /** * Removes selected screenshot. * */ - _removeScreenshot(event) { + removeScreenshot(event) { console.log(event.target) - } + }; render() { console.log(this.state.visibilityComponent); @@ -305,13 +319,23 @@ class Step2 extends Component {

+ onChange={this.onTextFieldChange.bind(this)} + /> +
+ +

+ onChange={this.onTextFieldChange.bind(this)} + /> +

+ onChange={this.onTextFieldChange.bind(this)} + /> +
-
+ +

+ onChange={this.handleTagChange.bind(this)} + onKeyPress={this.addTags.bind(this)} + /> +
- {this.state.tags.map(this._renderChip, this)} + {this.state.tags.map(this.renderChip, this)}

-
+ +
{/*Platform Specific Properties.*/}

Platform Specific Properties

@@ -374,50 +402,58 @@ class Step2 extends Component {

Banner*:

{this.state.banner.map((tile) => ( - - - }> - + + + }> + + ))} {this.state.banner.length === 0 ? - { - this.setState({banner, rejected}); - }}> -

+

-
:
} - + { + this.setState({banner, rejected}); + }} + > +

+

+
:
+ } -

{this.state.errors["Screenshots"]}

Screenshots*:

- + {this.state.screenshots.map((file) => ( - - - }> + + + }> ))} {this.state.screenshots.length < 3 ? - { - let tmpScreenshots = this.state.screenshots; - tmpScreenshots.push(screenshots); - this.setState({ - screenshots: tmpScreenshots}); - }}> -

+

-
:
} + { + let tmpScreenshots = this.state.screenshots; + tmpScreenshots.push(screenshots); + this.setState({ + screenshots: tmpScreenshots + }); + }} + > +

+

+
:
}

@@ -426,38 +462,43 @@ class Step2 extends Component {

Icon*:

{this.state.icon.map((tile) => ( - - - }> - + + + }> + + ))} {this.state.icon.length === 0 ? - {this.setState({icon, rejected});}}> -

+

-
:
} + { + this.setState({icon, rejected}); + }} + > +

+

+
:
}

-

diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/CreateSteps/Step3.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/CreateSteps/Step3.jsx index 1245d1df3c..2f77d3ab1d 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/CreateSteps/Step3.jsx +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Application/CreateSteps/Step3.jsx @@ -51,6 +51,10 @@ import Theme from '../../../theme'; class Step3 extends Component { constructor() { super(); + this.handleToggle = this.handleToggle.bind(this); + this.handlePrev = this.handlePrev.bind(this); + this.handleToggle = this.handleToggle.bind(this); + this.handleFinish = this.handleFinish.bind(this); this.state = { showForm: false, releaseChannel: 1, @@ -63,7 +67,7 @@ class Step3 extends Component { /** *Loading the theme files based on the the user-preference. */ - Theme.insertThemingScripts(this.scriptId); + Theme.insertThemingScripts(this.scriptId); } componentWillUnmount() { @@ -74,21 +78,21 @@ class Step3 extends Component { * Handles finish button click. * This invokes handleNext function in parent component. * */ - _handleFinish() { + handleFinish() { this.props.handleFinish(); } /** * Invokes Prev button click. * */ - _handlePrev() { + handlePrev() { this.props.handlePrev(); } /** * Handles release application selection. * */ - _handleToggle() { + handleToggle() { let hide = this.state.showForm; this.setState({showForm: !hide}); } @@ -100,36 +104,42 @@ class Step3 extends Component { {/*If toggle is true, the release form will be shown.*/} - {!this.state.showForm ?
:
- - - - -
-
-
} - + {!this.state.showForm ?
: +
+ + + + + +
+
+
}
- + +
diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Base/BaseLayout.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Base/BaseLayout.jsx index da98a075c5..a18813994a 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Base/BaseLayout.jsx +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Base/BaseLayout.jsx @@ -16,12 +16,17 @@ * under the License. */ +import Theme from '../../theme'; import PropTypes from 'prop-types'; import Badge from 'material-ui/Badge'; import React, {Component} from 'react'; import AppBar from 'material-ui/AppBar'; import Drawer from 'material-ui/Drawer'; +import IconMenu from 'material-ui/IconMenu'; +import MenuItem from 'material-ui/MenuItem'; import {withRouter} from 'react-router-dom'; +import AuthHandler from "../../api/authHandler"; +import FlatButton from 'material-ui/FlatButton'; import IconButton from 'material-ui/IconButton'; import {List, ListItem} from 'material-ui/List'; import Apps from 'material-ui/svg-icons/navigation/apps'; @@ -30,7 +35,6 @@ import Feedback from 'material-ui/svg-icons/action/feedback'; import DevicesOther from 'material-ui/svg-icons/hardware/devices-other'; import NotificationsIcon from 'material-ui/svg-icons/social/notifications'; import ActionAccountCircle from 'material-ui/svg-icons/action/account-circle'; -import Theme from '../../theme'; /** @@ -48,6 +52,7 @@ class BaseLayout extends Component { user: 'Admin' }; this.scriptId = "basic-layout"; + this.logout = this.logout.bind(this); } componentWillMount() { @@ -93,71 +98,93 @@ class BaseLayout extends Component { this.props.history.push(to); } + logout(event, index, value) { + AuthHandler.logout(); + } + render() { return (
- - - - - - - { - console.log("Clicked") - }}> - + + + + -
- } + + } + label="sdfdsf" + />} + anchorOrigin={{horizontal: 'left', vertical: 'top'}} + targetOrigin={{horizontal: 'left', vertical: 'top'}} + onChange={this.logout} + > + + + {/*}*/} + {/*onClick={() => {console.log("Clicked")}}*/} + {/*label={this.props.user.getUserName()}*/} + {/*/>*/} +
+ } />
- } - initiallyOpen={false} - primaryTogglesNestedList={true} - onClick={this.handleApplicationClick.bind(this)} - nestedItems={[ - } - />]} + } + initiallyOpen={false} + primaryTogglesNestedList={true} + onClick={this.handleApplicationClick.bind(this)} + nestedItems={[ + } + /> + ]} + /> + } + initiallyOpen={false} + primaryTogglesNestedList={true} + onClick={this.handlePlatformClick.bind(this)} + nestedItems={[ + } + /> + ]} /> - } - initiallyOpen={false} - primaryTogglesNestedList={true} - onClick={this.handlePlatformClick.bind(this)} - nestedItems={[ - } - />]} + } /> - }/>
{this.props.children}
-
); +
+ ); } - } BaseLayout.propTypes = { diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Overview/PublisherOverview.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Overview/PublisherOverview.jsx deleted file mode 100644 index c5ba536d06..0000000000 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Overview/PublisherOverview.jsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2017, 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. - */ -import React, {Component} from 'react'; -import DataTable from '../UIComponents/DataTable'; - -/** - * - * ***NEW*** - * The Publisher overview component. - * This component could be used to view app analytics. - * i.e number of overall downloads, ratings ect. - * */ -class PublisherOverview extends Component { - - constructor() { - super(); - } - - componentWillMount() { - } - - render() { - - return ( - -
- Overview - -
- ); - } -} - -export default PublisherOverview; \ No newline at end of file diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Platform/PlatformCreate.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Platform/PlatformCreate.jsx index 3c9c8acabf..e1fd2f8f3a 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Platform/PlatformCreate.jsx +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Platform/PlatformCreate.jsx @@ -16,7 +16,9 @@ * under the License. */ +import Theme from '../../theme'; import PropTypes from 'prop-types'; +import Chip from 'material-ui/Chip'; import Dropzone from 'react-dropzone'; import React, {Component} from 'react'; import Toggle from 'material-ui/Toggle'; @@ -26,12 +28,12 @@ import FlatButton from 'material-ui/FlatButton'; import IconButton from 'material-ui/IconButton'; import SelectField from 'material-ui/SelectField'; import RaisedButton from 'material-ui/RaisedButton'; +import PlatformMgtApi from '../../api/platformMgtApi'; import Clear from 'material-ui/svg-icons/content/clear'; import {GridList, GridTile} from 'material-ui/GridList'; import Close from 'material-ui/svg-icons/navigation/close'; import {Card, CardActions, CardTitle} from 'material-ui/Card'; import AddCircleOutline from 'material-ui/svg-icons/content/add-circle-outline'; -import Theme from '../../theme'; /** * Platform Create component. @@ -47,7 +49,20 @@ class PlatformCreate extends Component { constructor() { super(); + this.onCreatePlatform = this.onCreatePlatform.bind(this); + this.handleToggle = this.handleToggle.bind(this); + this.addProperty = this.addProperty.bind(this); + this.addTags = this.addTags.bind(this); + this.clearForm = this.clearForm.bind(this); + this.onPropertySelect = this.onPropertySelect.bind(this); + this.handleTagChange = this.handleTagChange.bind(this); + this.removeIcon = this.removeIcon.bind(this); + this.onTextChange = this.onTextChange.bind(this); + this.renderChip = this.renderChip.bind(this); + this.removeProperty = this.removeProperty.bind(this); this.state = { + tags: [], + defValue: "", enabled: true, allTenants: false, files: [], @@ -57,6 +72,7 @@ class PlatformCreate extends Component { description: "", property: "", icon: [], + identifier: "", propertyTypes: [ {key: 0, value: 'String'}, {key: 1, value: 'Number'}, @@ -70,7 +86,7 @@ class PlatformCreate extends Component { /** *Loading the theme files based on the the user-preference. */ - Theme.insertThemingScripts(this.scriptId); + Theme.insertThemingScripts(this.scriptId); } componentWillUnmount() { @@ -81,7 +97,7 @@ class PlatformCreate extends Component { * Handles toggle button actions. * One method is used for all the toggle buttons and, each toggle is identified by the id. * */ - _handleToggle(event) { + handleToggle(event) { switch (event.target.id) { case "enabled" : { let enabled = this.state.enabled; @@ -99,15 +115,64 @@ class PlatformCreate extends Component { /** * Triggers the onChange action on property type selection. * */ - _onPropertySelect = (event, index, value) => { + onPropertySelect(event, index, value) { console.log(this.state.propertyTypes[value]); this.setState({selectedProperty: value}); - }; + } + + /** + * Handles Chip delete function. + * Removes the tag from state.tags + * */ + handleTagDelete(key) { + this.chipData = this.state.tags; + const chipToDelete = this.chipData.map((chip) => chip.key).indexOf(key); + this.chipData.splice(chipToDelete, 1); + this.setState({tags: this.chipData}); + } + + /** + * Create a tag on Enter key press and set it to the state. + * Clears the tags text field. + * Chip gets two parameters: Key and value. + * */ + addTags(event) { + let tags = this.state.tags; + if (event.charCode === 13) { + event.preventDefault(); + tags.push({key: Math.floor(Math.random() * 1000), value: event.target.value}); + this.setState({tags, defValue: ""}); + } + } + + /** + * Creates Chip array from state.tags. + * */ + renderChip(data) { + return ( + this.handleTagDelete(data.key)} + style={this.styles.chip} + > + {data.value} + + ); + } + + /** + * Set the value for tag. + * */ + handleTagChange(event) { + let defaultValue = this.state.defValue; + defaultValue = event.target.value; + this.setState({defValue: defaultValue}) + } /** * Remove the selected property from the property list. * */ - _removeProperty(property) { + removeProperty(property) { let properties = this.state.platformProperties; properties.splice(properties.indexOf(property), 1); this.setState({platformProperties: properties}); @@ -116,28 +181,31 @@ class PlatformCreate extends Component { /** * Add a new platform property. * */ - _addProperty() { + addProperty() { let property = this.state.property; let selected = this.state.selectedProperty; - this.setState({platformProperties: - this.state.platformProperties.concat([ - { - key: property, - value: this.state.propertyTypes[selected].value - }]), + this.setState({ + platformProperties: + this.state.platformProperties.concat([ + { + key: property, + value: this.state.propertyTypes[selected].value + }]), property: "", - selectedProperty: 0}); + selectedProperty: 0 + }); } /** * Triggers in onChange event of text fields. * Text fields are identified by their ids and the value will be persisted in the component state. * */ - _onTextChange = (event, value) => { + onTextChange(event, value) { let property = this.state.property; let name = this.state.name; let description = this.state.description; + let identifier = this.state.identifier; switch (event.target.id) { case "name": { @@ -157,32 +225,57 @@ class PlatformCreate extends Component { this.setState({property: property}); break; } + case "identifier": { + identifier = value; + this.setState({identifier: identifier}); + } } }; - _onCreatePlatform() { + /** + * Create platform object and call the create platform api. + * */ + onCreatePlatform(event) { + //Call the platform create api. + event.preventDefault(); + let platform = {}; + platform.identifier = this.state.identifier; + platform.name = this.state.name; + platform.description = this.state.description; + platform.tags = this.state.tags; + platform.properties = this.state.platformProperties; + platform.icon = this.state.icon; + platform.enabled = this.state.enabled; + platform.allTenants = this.state.allTenants; + platform.defaultTenantMapping = true; + + PlatformMgtApi.createPlatform(platform); } /** * Remove the uploaded icon. * */ - _removeIcon(event) { + removeIcon(event) { + event.preventDefault(); this.setState({icon: []}); } /** * Clears the user entered values in the form. * */ - _clearForm() { - this.setState({enabled: true, + clearForm(event) { + event.preventDefault(); + this.setState({ + enabled: true, allTenants: false, files: [], platformProperties: [], selectedProperty: 0, name: "", description: "", - property: "",}) + property: "", + }) } render() { @@ -193,25 +286,38 @@ class PlatformCreate extends Component { selectedProperty, propertyTypes, name, + tags, + defValue, description, - property} = this.state; + identifier, + property + } = this.state; return (
-
+ +

+ onChange={this.onTextChange} + /> +


+ onChange={this.onTextChange} + /> +
+

+ /> +

+ /> +
+ +
+
+ {tags.map(this.renderChip, this)} +
+

Platform Properties

{platformProperties.map((p) => { return
{p.key} : {p.value} - +
@@ -254,21 +378,21 @@ class PlatformCreate extends Component { floatingLabelText="Platform Property*" floatingLabelFixed={true} value={this.state.property} - onChange={this._onTextChange.bind(this)} + onChange={this.onTextChange} /> + onChange={this.onPropertySelect}> {propertyTypes.map((type) => { - return + return })} - +
@@ -278,27 +402,33 @@ class PlatformCreate extends Component {

Platform Icon*:

{this.state.icon.map((tile) => ( - - -
}> + + + }> ))} {this.state.icon.length === 0 ? - {this.setState({icon, rejected})}}> + { + this.setState({icon, rejected}) + }} + >

+

-
:
} + :
}

- - + +
@@ -308,7 +438,6 @@ class PlatformCreate extends Component { } } -PlatformCreate.prototypes = { -}; +PlatformCreate.prototypes = {}; export default PlatformCreate; diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Platform/PlatformListing.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Platform/PlatformListing.jsx index 34387fca7e..97fc560f2a 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Platform/PlatformListing.jsx +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/Platform/PlatformListing.jsx @@ -16,12 +16,12 @@ * under the License. */ +import Theme from '../../theme'; import React, {Component} from 'react'; import {withRouter} from 'react-router-dom'; import TextField from 'material-ui/TextField'; import DataTable from '../UIComponents/DataTable'; import {Card, CardActions, CardTitle} from 'material-ui/Card'; -import Theme from '../../theme'; /** * The App Create Component. @@ -56,7 +56,7 @@ class PlatformListing extends Component { * Handles the search action. * When typing in the search bar, this method will be invoked. * */ - _searchApplications(word) { + searchApplications(word) { let searchedData = []; } @@ -64,13 +64,13 @@ class PlatformListing extends Component { * Handles sort data function and toggles the asc state. * asc: true : sort in ascending order. * */ - _sortData() { + sortData() { let isAsc = this.state.asc; - let datas = isAsc?this.data.sort(this._compare):this.data.reverse(); + let datas = isAsc ? this.data.sort(this.compare) : this.data.reverse(); this.setState({data: datas, asc: !isAsc}); } - _compare(a, b) { + compare(a, b) { if (a.applicationName < b.applicationName) return -1; if (a.applicationName > b.applicationName) @@ -78,27 +78,28 @@ class PlatformListing extends Component { return 0; } - _onRowClick(id) { + onRowClick(id) { console.log(id) } render() { return ( -
+
- - + - -
); +
+ ); } } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/UIComponents/DataTable.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/UIComponents/DataTable.jsx index beb6086b58..2b02a91af7 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/UIComponents/DataTable.jsx +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/UIComponents/DataTable.jsx @@ -16,13 +16,13 @@ * under the License. */ +import Theme from '../../theme'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; import DataTableRow from './DataTableRow'; import DataTableHeader from './DataTableHeader'; import RaisedButton from 'material-ui/RaisedButton'; import {Table, TableBody, TableHeader, TableRow} from 'material-ui/Table'; -import Theme from '../../theme'; /** * The Custom Table Component. @@ -53,6 +53,7 @@ class DataTable extends Component { constructor() { super(); + this.handleRowClick = this.handleRowClick.bind(this); this.state = { data: [], headers: [], @@ -83,7 +84,7 @@ class DataTable extends Component { * Triggers when user click on table row. * This method invokes the parent method handleRowClick, which is passed via props. * */ - _handleRowClick(id) { + handleRowClick(id) { this.props.handleRowClick(id); } @@ -100,22 +101,29 @@ class DataTable extends Component { if (data) { return ( - + selectable={false}> + {headers.map((header) => { - return () - } + return ( + + )} )} - {data.map((dataItem) =>{ - return () + {data.map((dataItem) => { + return ( + + ) })}
) diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/UIComponents/DataTableHeader.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/UIComponents/DataTableHeader.jsx index 8d655c6617..5d078ba59a 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/UIComponents/DataTableHeader.jsx +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/UIComponents/DataTableHeader.jsx @@ -16,11 +16,11 @@ * under the License. */ +import Theme from '../../theme'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; import FlatButton from 'material-ui/FlatButton'; import {TableHeaderColumn} from 'material-ui/Table'; -import Theme from '../../theme'; /** * Data Table header component. @@ -30,6 +30,7 @@ class DataTableHeader extends Component { constructor() { super(); + this.tableHeaderClick = this.tableHeaderClick.bind(this); this.scriptId = "data-table"; } @@ -42,13 +43,14 @@ class DataTableHeader extends Component { componentWillUnmount() { Theme.removeThemingScripts(this.scriptId); + } /** * The onClick function of the table header. * Invokes the function passed in the header object. * */ - _tableHeaderClick() { + tableHeaderClick() { this.props.header.sort(); } @@ -60,14 +62,18 @@ class DataTableHeader extends Component { * else create a span element with label as the table header. * */ if (this.props.header.sortable) { - headerCell = ; + headerCell = + } else { headerCell = {this.props.header.label}; } return ( - + {headerCell} ); diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/UIComponents/DataTableRow.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/UIComponents/DataTableRow.jsx index 292f0e4eab..7c9f8ea32f 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/UIComponents/DataTableRow.jsx +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/UIComponents/DataTableRow.jsx @@ -16,10 +16,10 @@ * under the License. */ +import Theme from '../../theme'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; import {TableRow, TableRowColumn} from 'material-ui/Table'; -import Theme from '../../theme'; /** * Data table row component. @@ -41,7 +41,7 @@ class DataTableRow extends Component { /** *Loading the theme files based on the the user-preference. */ - Theme.insertThemingScripts(this.scriptId); + Theme.insertThemingScripts(this.scriptId); } componentWillUnmount() { @@ -51,24 +51,32 @@ class DataTableRow extends Component { /** * Triggers the click event on the data table row. * */ - _handleClick() { + handleClick() { this.props.handleClick(this.state.dataItem.id); } render() { const {dataItem} = this.state; return ( - - {Object.keys(dataItem).map((key) => { - if (key !== 'id') { - return {dataItem[key]} - } else { - return - } + + {Object.keys(dataItem).map((key) => { + if (key !== 'id') { + return ( + + {dataItem[key]} + ) + } else { + return + } - } )} - + })} + ); } } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/User/Login/Login.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/User/Login/Login.jsx index 3b93e9e1b4..bdfb0c517c 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/User/Login/Login.jsx +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/User/Login/Login.jsx @@ -20,13 +20,11 @@ import qs from 'qs'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; import Checkbox from 'material-ui/Checkbox'; +import TextField from 'material-ui/TextField'; import {Redirect, Switch} from 'react-router-dom'; +import AuthHandler from '../../../api/authHandler'; import RaisedButton from 'material-ui/RaisedButton'; import {Card, CardActions, CardTitle} from 'material-ui/Card'; -import {TextValidator, ValidatorForm} from 'react-material-ui-form-validator'; - -//todo: remove the {TextValidator, ValidatorForm} and implement it manually. - /** * The Login Component. @@ -40,36 +38,43 @@ class Login extends Component { constructor() { super(); this.state = { - isLoggedIn: true, + isLoggedIn: false, referrer: "/", userName: "", password: "", - rememberMe: true + rememberMe: true, + errors: {} } } + componentWillMount() { + console.log("IN Login") + } + componentDidMount() { - let queryString = this.props.location.search; - console.log(queryString); - queryString = queryString.replace(/^\?/, ''); - /* With QS version up we can directly use {ignoreQueryPrefix: true} option */ - let params = qs.parse(queryString); - if (params.referrer) { - this.setState({referrer: params.referrer}); - } + console.log("in Login") + // let queryString = this.props.location.search; + // console.log(queryString); + // queryString = queryString.replace(/^\?/, ''); + // /* With QS version up we can directly use {ignoreQueryPrefix: true} option */ + // let params = qs.parse(queryString); + // if (params.referrer) { + // this.setState({referrer: params.referrer}); + // } } handleLogin(event) { event.preventDefault(); + this.validateForm(); } /** * Handles the username field change event. * */ - onUserNameChange(event) { + onUserNameChange(event, value) { this.setState( { - userName: event.target.value + userName: value } ); } @@ -77,10 +82,10 @@ class Login extends Component { /** * Handles the password field change event. * */ - onPasswordChange(event) { + onPasswordChange(event, value) { this.setState( { - password: event.target.value + password: value } ); } @@ -96,48 +101,77 @@ class Login extends Component { ); } + /** + * Validate the login form. + * */ + validateForm() { + let errors = {}; + let validationFailed = true; + if (!this.state.password) { + errors["passwordError"] = "Password is Required"; + validationFailed = true; + } else { + validationFailed = false; + } + + if (!this.state.userName) { + errors["userNameError"] = "User Name is Required"; + validationFailed = true; + } else { + validationFailed = false; + } + + if (validationFailed) { + this.setState({errors: errors}, console.log(errors)); + } else { + let loginPromis = AuthHandler.login(this.state.userName, this.state.password); + loginPromis.then(response => { + console.log(AuthHandler.getUser()); + this.setState({isLoggedIn: AuthHandler.getUser()}); + }) + } + } + render() { if (!this.state.isLoggedIn) { return (
- {/*TODO: Style the components.*/} - console.log(errors)}> - +
-
- +
-
+
); diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/index.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/index.js index a26e3415c0..788eb973da 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/index.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/components/index.js @@ -21,7 +21,6 @@ import NotFound from './Error/NotFound'; import BaseLayout from './Base/BaseLayout'; import PlatformCreate from './Platform/PlatformCreate'; import PlatformListing from './Platform/PlatformListing'; -import PublisherOverview from './Overview/PublisherOverview'; import ApplicationCreate from './Application/ApplicationCreate'; import ApplicationListing from './Application/ApplicationListing'; @@ -29,5 +28,4 @@ import ApplicationListing from './Application/ApplicationListing'; * Contains all UI components related to Application, Login and Platform */ -export {Login, BaseLayout, ApplicationCreate, ApplicationListing, PlatformListing, NotFound, PublisherOverview, - PlatformCreate}; +export {Login, BaseLayout, ApplicationCreate, ApplicationListing, PlatformListing, NotFound, PlatformCreate}; diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/theme.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/theme.js index 9243f7713e..45127271fd 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/theme.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/src/theme.js @@ -35,10 +35,10 @@ class Theme { //TODO Need to get the app context properly when the server is ready this.baseURL = window.location.origin; this.appContext = window.location.pathname.split("/")[1]; - this.loadThemeConfigs.bind(this); - this.loadThemeFiles.bind(this); - this.insertThemingScripts.bind(this); - this.removeThemingScripts.bind(this); + this.loadThemeConfigs = this.loadThemeConfigs.bind(this); + this.loadThemeFiles = this.loadThemeFiles.bind(this); + this.insertThemingScripts = this.insertThemingScripts.bind(this); + this.removeThemingScripts = this.removeThemingScripts.bind(this); } /** diff --git a/components/application-mgt/pom.xml b/components/application-mgt/pom.xml index 61523a4ba9..5b7f9cad9d 100644 --- a/components/application-mgt/pom.xml +++ b/components/application-mgt/pom.xml @@ -38,6 +38,7 @@ org.wso2.carbon.device.application.mgt.common org.wso2.carbon.device.application.mgt.api org.wso2.carbon.device.application.mgt.publisher.ui + org.wso2.carbon.device.application.mgt.authhandler diff --git a/features/application-mgt/org.wso2.carbon.device.application.mgt.auth.handler.feature/pom.xml b/features/application-mgt/org.wso2.carbon.device.application.mgt.auth.handler.feature/pom.xml new file mode 100644 index 0000000000..06e460a591 --- /dev/null +++ b/features/application-mgt/org.wso2.carbon.device.application.mgt.auth.handler.feature/pom.xml @@ -0,0 +1,120 @@ + + + + + org.wso2.carbon.devicemgt + application-mgt-feature + 3.0.46-SNAPSHOT + + + 4.0.0 + org.wso2.carbon.device.application.mgt.auth.handler.feature + 3.0.46-SNAPSHOT + pom + WSO2 Carbon - Application Management Authentication Handler Feature + This feature contains the Authentication Handler implementation for Publisher and Store. + http://wso2.org + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy + package + + copy + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.application.mgt.authhandler + + ${project.version} + war + true + + ${project.build.directory}/maven-shared-archive-resources/webapps + + auth#application-mgt#v1.0.war + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-resources + generate-resources + + copy-resources + + + src/main/resources + + + resources + + build.properties + p2.inf + + + + + + + + + org.wso2.maven + carbon-p2-plugin + + + p2-feature-generation + package + + p2-feature-gen + + + org.wso2.carbon.device.application.mgt.auth.handler + ../../../features/etc/feature.properties + + + + org.wso2.carbon.p2.category.type:server + + org.eclipse.equinox.p2.type.group:false + + + + + + + + + + diff --git a/features/application-mgt/org.wso2.carbon.device.application.mgt.auth.handler.feature/src/main/resources/build.properties b/features/application-mgt/org.wso2.carbon.device.application.mgt.auth.handler.feature/src/main/resources/build.properties new file mode 100644 index 0000000000..9c86577d76 --- /dev/null +++ b/features/application-mgt/org.wso2.carbon.device.application.mgt.auth.handler.feature/src/main/resources/build.properties @@ -0,0 +1 @@ +custom = true diff --git a/features/application-mgt/org.wso2.carbon.device.application.mgt.auth.handler.feature/src/main/resources/p2.inf b/features/application-mgt/org.wso2.carbon.device.application.mgt.auth.handler.feature/src/main/resources/p2.inf new file mode 100644 index 0000000000..8cc8bbdfd5 --- /dev/null +++ b/features/application-mgt/org.wso2.carbon.device.application.mgt.auth.handler.feature/src/main/resources/p2.inf @@ -0,0 +1,3 @@ +instructions.configure = \ +org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../deployment/server/webapps/);\ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.device.application.mgt.auth.handler_${feature.version}/webapps/auth#application-mgt#v1.0.war,target:${installFolder}/../../deployment/server/webapps/auth#application-mgt#v1.0.war,overwrite:true);\ \ No newline at end of file diff --git a/features/application-mgt/pom.xml b/features/application-mgt/pom.xml index fc0e07a23a..aff7d9c9c7 100644 --- a/features/application-mgt/pom.xml +++ b/features/application-mgt/pom.xml @@ -35,10 +35,12 @@ org.wso2.carbon.device.application.mgt.api.feature - org.wso2.carbon.device.application.mgt.server.feature org.wso2.carbon.device.application.mgt.publisher.ui.feature + org.wso2.carbon.device.application.mgt.auth.handler.feature org.wso2.carbon.device.application.mgt.feature + org.wso2.carbon.device.application.mgt.server.feature + \ No newline at end of file