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 all 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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Apollo 2.2.0
* [[Multi-Database Support][h2] Support run on h2](https://github.com/apolloconfig/apollo/pull/4851)
* [Fix the issue that env special case handling is missing in some case](https://github.com/apolloconfig/apollo/pull/4887)
* [Fix the issue that namespace content being cleared when identical content is pasted into the namespace](https://github.com/apolloconfig/apollo/pull/4922)
* [feat(openapi): allow user create app via openapi](https://github.com/apolloconfig/apollo/pull/4954)

------------------
All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/13?closed=1)
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ public BadRequestException(String msgtpl, Object... args) {
setHttpStatus(HttpStatus.BAD_REQUEST);
}

public static BadRequestException ownerNameIsBlank() {
return new BadRequestException("ownerName can not be blank");
}

public static BadRequestException orgIdIsBlank() {
return new BadRequestException("orgId can not be blank");
}

public static BadRequestException itemAlreadyExists(String itemKey) {
return new BadRequestException("item already exists for itemKey:%s", itemKey);
}
Expand Down Expand Up @@ -92,6 +100,14 @@ public static BadRequestException appAlreadyExists(String appId) {
return new BadRequestException("app already exists for appId:%s", appId);
}

public static BadRequestException appIdIsBlank() {
return new BadRequestException("appId can not be blank");
}

public static BadRequestException appNameIsBlank() {
return new BadRequestException("app name can not be blank");
}

public static BadRequestException clusterNotExists(String clusterName) {
return new BadRequestException("cluster not exists for clusterName:%s", clusterName);
}
Expand Down
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 @@ -21,9 +21,11 @@
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.util.OpenApiBeanUtils;
import com.ctrip.framework.apollo.portal.component.PortalSettings;
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;
Expand All @@ -37,6 +39,7 @@
*/
@Service
public class ServerAppOpenApiService implements AppOpenApiService {

private final PortalSettings portalSettings;
private final ClusterService clusterService;
private final AppService appService;
Expand All @@ -50,6 +53,26 @@ public ServerAppOpenApiService(
this.appService = appService;
}

private App convert(OpenAppDTO 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.getApp());
appService.createAppAndAddRolePermission(app, req.getAdmins());
}

@Override
public List<OpenEnvClusterDTO> getEnvClusterInfo(String appId) {
List<OpenEnvClusterDTO> envClusters = new LinkedList<>();
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 All @@ -28,6 +31,7 @@
import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.entity.po.Role;
import com.ctrip.framework.apollo.portal.entity.vo.consumer.ConsumerInfo;
import com.ctrip.framework.apollo.portal.repository.RoleRepository;
import com.ctrip.framework.apollo.portal.service.RolePermissionService;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
Expand All @@ -38,6 +42,8 @@
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.hash.Hashing;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Objects;
import org.apache.commons.lang3.time.FastDateFormat;
import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -186,9 +192,97 @@ public List<ConsumerRole> assignNamespaceRoleToConsumer(String token, String app
return Arrays.asList(createdModifyConsumerRole, createdReleaseConsumerRole);
}

private ConsumerInfo convert(
Consumer consumer,
String token,
boolean allowCreateApplication
) {
ConsumerInfo consumerInfo = new ConsumerInfo();
consumerInfo.setConsumerId(consumer.getId());
consumerInfo.setAppId(consumer.getAppId());
consumerInfo.setName(consumer.getName());
consumerInfo.setOwnerName(consumer.getOwnerName());
consumerInfo.setOwnerEmail(consumer.getOwnerEmail());
consumerInfo.setOrgId(consumer.getOrgId());
consumerInfo.setOrgName(consumer.getOrgName());

consumerInfo.setToken(token);
consumerInfo.setAllowCreateApplication(allowCreateApplication);
return consumerInfo;
}

public ConsumerInfo getConsumerInfoByAppId(String appId) {
ConsumerToken consumerToken = getConsumerTokenByAppId(appId);
if (null == consumerToken) {
return null;
}
Consumer consumer = consumerRepository.findByAppId(appId);
if (consumer == null) {
return null;
}
return convert(consumer, consumerToken.getToken(), isAllowCreateApplication(consumer.getId()));
}

private boolean isAllowCreateApplication(Long consumerId) {
return isAllowCreateApplication(Collections.singletonList(consumerId)).get(0);
}

private List<Boolean> isAllowCreateApplication(List<Long> consumerIdList) {
Role createAppRole = getCreateAppRole();
if (createAppRole == null) {
List<Boolean> list = new ArrayList<>(consumerIdList.size());
for (Long ignored : consumerIdList) {
list.add(false);
}
return list;
}

long roleId = createAppRole.getId();
List<Boolean> list = new ArrayList<>(consumerIdList.size());
for (Long consumerId : consumerIdList) {
ConsumerRole createAppConsumerRole = consumerRoleRepository.findByConsumerIdAndRoleId(
consumerId, roleId
);
list.add(createAppConsumerRole != null);
}

return list;
}

private Role getCreateAppRole() {
return rolePermissionService.findRoleByRoleName(CREATE_APPLICATION_ROLE_NAME);
}

public ConsumerRole assignCreateApplicationRoleToConsumer(String token) {
Long consumerId = getConsumerIdByToken(token);
if (consumerId == null) {
throw new BadRequestException("Token is Illegal");
}
Role createAppRole = getCreateAppRole();
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 Expand Up @@ -295,10 +389,30 @@ private Set<String> findAppIdsByRoleIds(List<Long> roleIds) {
return appIds;
}

public List<Consumer> findAllConsumer(Pageable page){
List<Consumer> findAllConsumer(Pageable page){
return this.consumerRepository.findAll(page).getContent();
}

public List<ConsumerInfo> findConsumerInfoList(Pageable page) {
List<Consumer> consumerList = findAllConsumer(page);
List<Long> consumerIdList = consumerList.stream()
.map(Consumer::getId).collect(Collectors.toList());
List<Boolean> allowCreateApplicationList = isAllowCreateApplication(consumerIdList);

List<ConsumerInfo> consumerInfoList = new ArrayList<>(consumerList.size());

for (int i = 0; i < consumerList.size(); i++) {
Consumer consumer = consumerList.get(i);
// without token
ConsumerInfo consumerInfo = convert(
consumer, null, allowCreateApplicationList.get(i)
);
consumerInfoList.add(consumerInfo);
}

return consumerInfoList;
}

@Transactional
public void deleteConsumer(String appId){
Consumer consumer = consumerRepository.findByAppId(appId);
Expand Down
Loading