Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(openapi): allow user create app via openapi #4954

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
06b2643
feat(openapi): allow user create app via openapi
Anilople Aug 11, 2023
6e510c0
Update apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi…
Anilople Aug 12, 2023
913778c
delete create app in one env; refactor the logic of create app
Anilople Aug 16, 2023
24ea949
Merge branch 'feat/portal-allow-openapi-create-app' of https://github…
Anilople Aug 16, 2023
cbaa5e9
refactor: change createAppInLocal to private and remove '@Transactional'
Anilople Aug 18, 2023
ab85347
remove useless field
Anilople Aug 18, 2023
911a2fa
remove useless check
Anilople Aug 18, 2023
b4f391e
refactor: path "apps/create" -> "apps"
Anilople Aug 18, 2023
095a7ee
sync client's change
Anilople Aug 27, 2023
dfae373
feat: allow assignAppRoleToConsumer when use openapi
Anilople Aug 28, 2023
2b1131c
Update ServerAppOpenApiService.java
Anilople Aug 28, 2023
9805d9f
feat: add '@Transactional' to controller method
Anilople Aug 29, 2023
1e7941b
move Transactional position and delete rollbackOn
Anilople Aug 29, 2023
4f717ee
Merge branch 'master' into feat/portal-allow-openapi-create-app
Anilople Sep 2, 2023
10e973e
feat: allow assignCreateApplicationRoleToConsumer when create consumer
Anilople Sep 10, 2023
19ce123
fix: button don't work
Anilople Sep 10, 2023
474cbfd
Update apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi…
Anilople Sep 17, 2023
5d0dae1
Update apollo-portal/src/main/resources/static/i18n/en.json
Anilople Sep 17, 2023
5bef9a2
Update apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/…
Anilople Sep 17, 2023
901aab6
Apply suggestions from code review
Anilople Sep 17, 2023
5f209b2
remove red style
Anilople Sep 17, 2023
bf8dfd9
change width same as table header
Anilople Sep 17, 2023
6fcf5df
fix: submitBtnDisabled should change to false after warning user
Anilople Sep 17, 2023
978b350
test: Consumer create and app create
Anilople Sep 17, 2023
9249c7a
change to checkbox
Anilople Sep 17, 2023
cb81300
upgrade to junit5
Anilople Sep 17, 2023
f6778e6
test: add test of isAllowCreateApplication
Anilople Sep 17, 2023
20a860f
remove apollorequiredfield
Anilople Sep 19, 2023
075c7a4
Update CHANGES.md
Anilople Sep 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,10 @@ public static NotFoundException appNotFound(String appId) {
return new NotFoundException("app not found for appId:%s", appId);
}

public static NotFoundException roleNotFound(String roleName) {
return new NotFoundException(
"role not found for roleName:%s, please check apollo portal DB table 'Role'",
roleName
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
*/
package com.ctrip.framework.apollo.common.exception;

import org.junit.Assert;
import org.junit.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

public class NotFoundExceptionTest {

Expand All @@ -33,49 +34,54 @@ public void testConstructor() {
clusterName, namespaceName, key);
e2 = new NotFoundException(
String.format("item not found for %s %s %s %s", appId, clusterName, namespaceName, key));
Assert.assertEquals(e1.getMessage(), e2.getMessage());
assertEquals(e1.getMessage(), e2.getMessage());
}

@Test
public void testAppNotFoundException() {
NotFoundException exception = NotFoundException.appNotFound(appId);
Assert.assertEquals(exception.getMessage(), "app not found for appId:app-1001");
assertEquals(exception.getMessage(), "app not found for appId:app-1001");
}

@Test
public void testClusterNotFoundException() {
NotFoundException exception = NotFoundException.clusterNotFound(appId, clusterName);
Assert.assertEquals(exception.getMessage(), "cluster not found for appId:app-1001 clusterName:test");
assertEquals(exception.getMessage(), "cluster not found for appId:app-1001 clusterName:test");
}

@Test
public void testNamespaceNotFoundException() {
NotFoundException exception = NotFoundException.namespaceNotFound(appId, clusterName, namespaceName);
Assert.assertEquals(exception.getMessage(), "namespace not found for appId:app-1001 clusterName:test namespaceName:application");
assertEquals(exception.getMessage(), "namespace not found for appId:app-1001 clusterName:test namespaceName:application");

exception = NotFoundException.namespaceNotFound(66);
Assert.assertEquals(exception.getMessage(), "namespace not found for namespaceId:66");
assertEquals(exception.getMessage(), "namespace not found for namespaceId:66");
}

@Test
public void testReleaseNotFoundException() {
NotFoundException exception = NotFoundException.releaseNotFound(66);
Assert.assertEquals(exception.getMessage(), "release not found for releaseId:66");
assertEquals(exception.getMessage(), "release not found for releaseId:66");
}

@Test
public void testItemNotFoundException(){
NotFoundException exception = NotFoundException.itemNotFound(66);
Assert.assertEquals(exception.getMessage(), "item not found for itemId:66");
assertEquals(exception.getMessage(), "item not found for itemId:66");

exception = NotFoundException.itemNotFound("test.key");
Assert.assertEquals(exception.getMessage(), "item not found for itemKey:test.key");
assertEquals(exception.getMessage(), "item not found for itemKey:test.key");

exception = NotFoundException.itemNotFound(appId, clusterName, namespaceName, "test.key");
Assert.assertEquals(exception.getMessage(), "item not found for appId:app-1001 clusterName:test namespaceName:application itemKey:test.key");
assertEquals(exception.getMessage(), "item not found for appId:app-1001 clusterName:test namespaceName:application itemKey:test.key");

exception = NotFoundException.itemNotFound(appId, clusterName, namespaceName, 66);
Assert.assertEquals(exception.getMessage(), "item not found for appId:app-1001 clusterName:test namespaceName:application itemId:66");
assertEquals(exception.getMessage(), "item not found for appId:app-1001 clusterName:test namespaceName:application itemId:66");
}

@Test
void roleNotFound() {
NotFoundException exception = NotFoundException.roleNotFound("CreateApplication+SystemRole");
assertEquals(exception.getMessage(), "role not found for roleName:CreateApplication+SystemRole, please check apollo portal DB table 'Role'");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package com.ctrip.framework.apollo.openapi.auth;

import static com.ctrip.framework.apollo.portal.service.SystemRoleManagerService.SYSTEM_PERMISSION_TARGET_ID;

import com.ctrip.framework.apollo.openapi.service.ConsumerRolePermissionService;
import com.ctrip.framework.apollo.openapi.util.ConsumerAuthUtil;
import com.ctrip.framework.apollo.portal.constant.PermissionType;
Expand Down Expand Up @@ -70,4 +72,9 @@ public boolean hasCreateClusterPermission(HttpServletRequest request, String app
return permissionService.consumerHasPermission(consumerAuthUtil.retrieveConsumerId(request),
PermissionType.CREATE_CLUSTER, appId);
}

public boolean hasCreateApplicationPermission(HttpServletRequest request) {
long consumerId = consumerAuthUtil.retrieveConsumerId(request);
return permissionService.consumerHasPermission(consumerId, PermissionType.CREATE_APPLICATION, SYSTEM_PERMISSION_TARGET_ID);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,22 @@
package com.ctrip.framework.apollo.openapi.server.service;

import com.ctrip.framework.apollo.common.dto.ClusterDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.entity.App;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.openapi.api.AppOpenApiService;
import com.ctrip.framework.apollo.openapi.dto.OpenAppDTO;
import com.ctrip.framework.apollo.openapi.dto.OpenCreateAppDTO;
import com.ctrip.framework.apollo.openapi.dto.OpenEnvClusterDTO;
import com.ctrip.framework.apollo.openapi.service.ConsumerService;
import com.ctrip.framework.apollo.openapi.util.OpenApiBeanUtils;
import com.ctrip.framework.apollo.portal.component.PortalSettings;
import com.ctrip.framework.apollo.portal.controller.ConsumerController;
import com.ctrip.framework.apollo.portal.entity.model.AppModel;
import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.service.AppService;
import com.ctrip.framework.apollo.portal.service.ClusterService;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
Expand All @@ -37,17 +43,48 @@
*/
@Service
public class ServerAppOpenApiService implements AppOpenApiService {

private final PortalSettings portalSettings;
private final ClusterService clusterService;
private final AppService appService;

private final ConsumerService consumerService;

public ServerAppOpenApiService(
PortalSettings portalSettings,
ClusterService clusterService,
AppService appService) {
AppService appService, ConsumerService consumerService) {
this.portalSettings = portalSettings;
this.clusterService = clusterService;
this.appService = appService;
this.consumerService = consumerService;
}

private App convert(OpenCreateAppDTO dto) {
return App.builder()
.appId(dto.getAppId())
.name(dto.getName())
.ownerName(dto.getOwnerName())
.orgId(dto.getOrgId())
.orgName(dto.getOrgName())
.ownerEmail(dto.getOwnerEmail())
.build();
}

/**
* @see com.ctrip.framework.apollo.portal.controller.AppController#create(AppModel)
*/
@Override
public void createApp(OpenCreateAppDTO req) {
App app = convert(req);
appService.createAppAndAddRolePermission(app, req.getAdmins());
}

/**
* @see ConsumerController#assignNamespaceRoleToConsumer(String, boolean, String, String, NamespaceDTO)
*/
private void assignAppRoleToConsumer(String token, String appId) {
consumerService.assignAppRoleToConsumer(token, appId);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
*/
package com.ctrip.framework.apollo.openapi.service;

import static com.ctrip.framework.apollo.portal.service.SystemRoleManagerService.CREATE_APPLICATION_ROLE_NAME;

import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.openapi.entity.Consumer;
import com.ctrip.framework.apollo.openapi.entity.ConsumerAudit;
import com.ctrip.framework.apollo.openapi.entity.ConsumerRole;
Expand Down Expand Up @@ -186,9 +189,37 @@ public List<ConsumerRole> assignNamespaceRoleToConsumer(String token, String app
return Arrays.asList(createdModifyConsumerRole, createdReleaseConsumerRole);
}

@Transactional
public ConsumerRole assignCreateApplicationRoleToConsumer(String token) {
Long consumerId = getConsumerIdByToken(token);
if (consumerId == null) {
throw new BadRequestException("Token is Illegal");
}
Role createAppRole = rolePermissionService.findRoleByRoleName(CREATE_APPLICATION_ROLE_NAME);
Anilople marked this conversation as resolved.
Show resolved Hide resolved
if (createAppRole == null) {
throw NotFoundException.roleNotFound(CREATE_APPLICATION_ROLE_NAME);
}

long roleId = createAppRole.getId();
ConsumerRole createAppConsumerRole = consumerRoleRepository.findByConsumerIdAndRoleId(consumerId, roleId);
if (createAppConsumerRole != null) {
return createAppConsumerRole;
}

String operator = userInfoHolder.getUser().getUserId();
ConsumerRole consumerRole = createConsumerRole(consumerId, roleId, operator);
return consumerRoleRepository.save(consumerRole);
}


@Transactional
public ConsumerRole assignAppRoleToConsumer(String token, String appId) {
Long consumerId = getConsumerIdByToken(token);
return assignAppRoleToConsumer(consumerId, appId);
}

@Transactional
public ConsumerRole assignAppRoleToConsumer(Long consumerId, String appId) {
if (consumerId == null) {
throw new BadRequestException("Token is Illegal");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@
*/
package com.ctrip.framework.apollo.openapi.v1.controller;

import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.openapi.api.AppOpenApiService;
import com.ctrip.framework.apollo.openapi.dto.OpenCreateAppDTO;
import com.ctrip.framework.apollo.openapi.service.ConsumerService;
import com.ctrip.framework.apollo.openapi.util.ConsumerAuthUtil;
import com.ctrip.framework.apollo.openapi.dto.OpenAppDTO;
import com.ctrip.framework.apollo.openapi.dto.OpenEnvClusterDTO;
import com.ctrip.framework.apollo.portal.entity.model.AppModel;
import java.util.Arrays;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

Expand All @@ -47,6 +51,30 @@ public AppController(
this.appOpenApiService = appOpenApiService;
}

/**
* @see com.ctrip.framework.apollo.portal.controller.AppController#create(AppModel)
*/
@PreAuthorize(value = "@consumerPermissionValidator.hasCreateApplicationPermission(#request)")
@PostMapping(value = "/apps")
public void createApp(
@RequestBody OpenCreateAppDTO req,
HttpServletRequest request
) {
if (null == req.getAppId()) {
throw new BadRequestException("AppId is null");
}
if (req.isAssignAppRoleToSelf()) {
// create app and assign app role to this consumer
this.appOpenApiService.createApp(req);
// todo @Transactional
long consumerId = this.consumerAuthUtil.retrieveConsumerId(request);
consumerService.assignAppRoleToConsumer(consumerId, req.getAppId());
nobodyiam marked this conversation as resolved.
Show resolved Hide resolved
} else {
// only create app
this.appOpenApiService.createApp(req);
}
}

@GetMapping(value = "/apps/{appId}/envclusters")
public List<OpenEnvClusterDTO> getEnvClusterInfo(@PathVariable String appId){
return this.appOpenApiService.getEnvClusterInfo(appId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import com.ctrip.framework.apollo.portal.entity.po.Role;
import com.ctrip.framework.apollo.portal.entity.vo.EnvClusterInfo;
import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.listener.AppCreationEvent;
import com.ctrip.framework.apollo.portal.listener.AppDeletionEvent;
import com.ctrip.framework.apollo.portal.listener.AppInfoChangedEvent;
import com.ctrip.framework.apollo.portal.service.AdditionalUserInfoEnrichService;
Expand All @@ -46,7 +45,6 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand Down Expand Up @@ -124,19 +122,7 @@ public List<App> findAppsByOwner(@RequestParam("owner") String owner, Pageable p
public App create(@Valid @RequestBody AppModel appModel) {

App app = transformToApp(appModel);

App createdApp = appService.createAppInLocal(app);

publisher.publishEvent(new AppCreationEvent(createdApp));

Set<String> admins = appModel.getAdmins();
if (!CollectionUtils.isEmpty(admins)) {
rolePermissionService
.assignRoleToUsers(RoleUtils.buildAppMasterRoleName(createdApp.getAppId()),
admins, userInfoHolder.getUser().getUserId());
}

return createdApp;
return appService.createAppAndAddRolePermission(app, appModel.getAdmins());
}

@PreAuthorize(value = "@permissionValidator.isAppAdmin(#appId)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,20 @@ public ConsumerToken getConsumerTokenByAppId(@RequestParam String appId) {

@PreAuthorize(value = "@permissionValidator.isSuperAdmin()")
@PostMapping(value = "/consumers/{token}/assign-role")
public List<ConsumerRole> assignNamespaceRoleToConsumer(@PathVariable String token,
@RequestParam String type,
@RequestParam(required = false) String envs,
@RequestBody NamespaceDTO namespace) {
public List<ConsumerRole> assignNamespaceRoleToConsumer(
@PathVariable String token,
@RequestParam(required = false, defaultValue = "false") boolean isAllowCreateApplication,
@RequestParam String type,
@RequestParam(required = false) String envs,
@RequestBody NamespaceDTO namespace
) {
Anilople marked this conversation as resolved.
Show resolved Hide resolved
List<ConsumerRole> consumerRoleList = new ArrayList<>(8);
if (isAllowCreateApplication) {
nobodyiam marked this conversation as resolved.
Show resolved Hide resolved
// when openapi create an app, it will become AppRole of this app too.
ConsumerRole consumerRole = consumerService.assignCreateApplicationRoleToConsumer(token);
consumerRoleList.add(consumerRole);
// no return here because need add another role to this consumer
}

String appId = namespace.getAppId();
String namespaceName = namespace.getNamespaceName();
Expand Down Expand Up @@ -117,7 +127,10 @@ public List<ConsumerRole> assignNamespaceRoleToConsumer(@PathVariable String tok
return consumeRoles;
}

return consumerService.assignNamespaceRoleToConsumer(token, appId, namespaceName);
consumerRoleList.addAll(
consumerService.assignNamespaceRoleToConsumer(token, appId, namespaceName)
);
return consumerRoleList;
}

@GetMapping("/consumers")
Expand Down
Loading
Loading