Commit 32aac0bd authored by nisiyong's avatar nisiyong Committed by Jason Song

Add apollo-client Authentication function

parent f16274a3
package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.AccessKey;
import com.ctrip.framework.apollo.biz.service.AccessKeyService;
import com.ctrip.framework.apollo.common.dto.AccessKeyDTO;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import java.util.List;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @author nisiyong
*/
@RestController
public class AccessKeyController {
private final AccessKeyService accessKeyService;
public AccessKeyController(
AccessKeyService accessKeyService) {
this.accessKeyService = accessKeyService;
}
@PostMapping(value = "/apps/{appId}/accesskeys")
public AccessKeyDTO create(@PathVariable String appId, @RequestBody AccessKeyDTO dto) {
AccessKey entity = BeanUtils.transform(AccessKey.class, dto);
entity = accessKeyService.create(appId, entity);
return BeanUtils.transform(AccessKeyDTO.class, entity);
}
@GetMapping(value = "/apps/{appId}/accesskeys")
public List<AccessKeyDTO> findByAppId(@PathVariable String appId) {
List<AccessKey> accessKeyList = accessKeyService.findByAppId(appId);
return BeanUtils.batchTransform(AccessKeyDTO.class, accessKeyList);
}
@DeleteMapping(value = "/apps/{appId}/accesskeys/{id}")
public void delete(@PathVariable String appId, @PathVariable long id, String operator) {
accessKeyService.delete(appId, id, operator);
}
@PutMapping(value = "/apps/{appId}/accesskeys/{id}/enable")
public void enable(@PathVariable String appId, @PathVariable long id, String operator) {
AccessKey entity = new AccessKey();
entity.setId(id);
entity.setEnabled(true);
entity.setDataChangeLastModifiedBy(operator);
accessKeyService.update(appId, entity);
}
@PutMapping(value = "/apps/{appId}/accesskeys/{id}/disable")
public void disable(@PathVariable String appId, @PathVariable long id, String operator) {
AccessKey entity = new AccessKey();
entity.setId(id);
entity.setEnabled(false);
entity.setDataChangeLastModifiedBy(operator);
accessKeyService.update(appId, entity);
}
}
......@@ -7,13 +7,12 @@ import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.springframework.stereotype.Component;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.springframework.stereotype.Component;
@Component
public class BizConfig extends RefreshableConfig {
......@@ -23,6 +22,8 @@ public class BizConfig extends RefreshableConfig {
private static final int DEFAULT_APPNAMESPACE_CACHE_REBUILD_INTERVAL = 60; //60s
private static final int DEFAULT_GRAY_RELEASE_RULE_SCAN_INTERVAL = 60; //60s
private static final int DEFAULT_APPNAMESPACE_CACHE_SCAN_INTERVAL = 1; //1s
private static final int DEFAULT_ACCESSKEY_CACHE_SCAN_INTERVAL = 1; //1s
private static final int DEFAULT_ACCESSKEY_CACHE_REBUILD_INTERVAL = 60; //60s
private static final int DEFAULT_RELEASE_MESSAGE_CACHE_SCAN_INTERVAL = 1; //1s
private static final int DEFAULT_RELEASE_MESSAGE_SCAN_INTERVAL_IN_MS = 1000; //1000ms
private static final int DEFAULT_RELEASE_MESSAGE_NOTIFICATION_BATCH = 100;
......@@ -119,6 +120,24 @@ public class BizConfig extends RefreshableConfig {
return TimeUnit.SECONDS;
}
public int accessKeyCacheScanInterval() {
int interval = getIntProperty("apollo.access-key-cache-scan.interval", DEFAULT_ACCESSKEY_CACHE_SCAN_INTERVAL);
return checkInt(interval, 1, Integer.MAX_VALUE, DEFAULT_ACCESSKEY_CACHE_SCAN_INTERVAL);
}
public TimeUnit accessKeyCacheScanIntervalTimeUnit() {
return TimeUnit.SECONDS;
}
public int accessKeyCacheRebuildInterval() {
int interval = getIntProperty("apollo.access-key-cache-rebuild.interval", DEFAULT_ACCESSKEY_CACHE_REBUILD_INTERVAL);
return checkInt(interval, 1, Integer.MAX_VALUE, DEFAULT_ACCESSKEY_CACHE_REBUILD_INTERVAL);
}
public TimeUnit accessKeyCacheRebuildIntervalTimeUnit() {
return TimeUnit.SECONDS;
}
public int releaseMessageCacheScanInterval() {
int interval = getIntProperty("apollo.release-message-cache-scan.interval", DEFAULT_RELEASE_MESSAGE_CACHE_SCAN_INTERVAL);
return checkInt(interval, 1, Integer.MAX_VALUE, DEFAULT_RELEASE_MESSAGE_CACHE_SCAN_INTERVAL);
......
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "AccessKey")
@SQLDelete(sql = "Update AccessKey set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class AccessKey extends BaseEntity {
@Column(name = "appId", nullable = false)
private String appId;
@Column(name = "Secret", nullable = false)
private String secret;
@Column(name = "isEnabled", columnDefinition = "Bit default '0'")
private boolean enabled;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public String toString() {
return toStringHelper().add("appId", appId).add("secret", secret)
.add("enabled", enabled).toString();
}
}
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.AccessKey;
import java.util.Date;
import java.util.List;
import org.springframework.data.repository.PagingAndSortingRepository;
public interface AccessKeyRepository extends PagingAndSortingRepository<AccessKey, Long> {
long countByAppId(String appId);
AccessKey findOneByAppIdAndId(String appId, long id);
List<AccessKey> findByAppId(String appId);
List<AccessKey> findFirst500ByDataChangeLastModifiedTimeGreaterThanOrderByDataChangeLastModifiedTimeAsc(Date date);
List<AccessKey> findByDataChangeLastModifiedTime(Date date);
}
package com.ctrip.framework.apollo.biz.service;
import com.ctrip.framework.apollo.biz.entity.AccessKey;
import com.ctrip.framework.apollo.biz.entity.Audit;
import com.ctrip.framework.apollo.biz.repository.AccessKeyRepository;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author nisiyong
*/
@Service
public class AccessKeyService {
private static final int ACCESSKEY_COUNT_LIMIT = 5;
private final AccessKeyRepository accessKeyRepository;
private final AuditService auditService;
public AccessKeyService(
AccessKeyRepository accessKeyRepository,
AuditService auditService) {
this.accessKeyRepository = accessKeyRepository;
this.auditService = auditService;
}
public List<AccessKey> findByAppId(String appId) {
return accessKeyRepository.findByAppId(appId);
}
@Transactional
public AccessKey create(String appId, AccessKey entity) {
long count = accessKeyRepository.countByAppId(appId);
if (count >= ACCESSKEY_COUNT_LIMIT) {
throw new BadRequestException("AccessKeys count limit exceeded");
}
entity.setId(0L);
entity.setAppId(appId);
entity.setDataChangeLastModifiedBy(entity.getDataChangeCreatedBy());
AccessKey accessKey = accessKeyRepository.save(entity);
auditService.audit(AccessKey.class.getSimpleName(), accessKey.getId(), Audit.OP.INSERT,
accessKey.getDataChangeCreatedBy());
return accessKey;
}
@Transactional
public AccessKey update(String appId, AccessKey entity) {
long id = entity.getId();
String operator = entity.getDataChangeLastModifiedBy();
AccessKey accessKey = accessKeyRepository.findOneByAppIdAndId(appId, id);
if (accessKey == null) {
throw new BadRequestException("AccessKey not exist");
}
accessKey.setEnabled(entity.isEnabled());
accessKey.setDataChangeLastModifiedBy(operator);
accessKeyRepository.save(accessKey);
auditService.audit(AccessKey.class.getSimpleName(), id, Audit.OP.UPDATE, operator);
return accessKey;
}
@Transactional
public void delete(String appId, long id, String operator) {
AccessKey accessKey = accessKeyRepository.findOneByAppIdAndId(appId, id);
if (accessKey == null) {
throw new BadRequestException("AccessKey not exist");
}
if (accessKey.isEnabled()) {
throw new BadRequestException("AccessKey should disable first");
}
accessKey.setDeleted(Boolean.TRUE);
accessKey.setDataChangeLastModifiedBy(operator);
accessKeyRepository.save(accessKey);
auditService.audit(AccessKey.class.getSimpleName(), id, Audit.OP.DELETE, operator);
}
}
package com.ctrip.framework.apollo.biz.repository;
import static org.assertj.core.api.Assertions.assertThat;
import com.ctrip.framework.apollo.biz.AbstractIntegrationTest;
import com.ctrip.framework.apollo.biz.entity.AccessKey;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
public class AccessKeyRepositoryTest extends AbstractIntegrationTest {
@Autowired
private AccessKeyRepository accessKeyRepository;
@Test
public void testSave() {
String appId = "someAppId";
String secret = "someSecret";
AccessKey entity = new AccessKey();
entity.setAppId(appId);
entity.setSecret(secret);
AccessKey accessKey = accessKeyRepository.save(entity);
assertThat(accessKey).isNotNull();
assertThat(accessKey.getAppId()).isEqualTo(appId);
assertThat(accessKey.getSecret()).isEqualTo(secret);
}
@Test
@Sql(scripts = "/sql/accesskey-test.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
public void testFindByAppId() {
String appId = "someAppId";
List<AccessKey> accessKeyList = accessKeyRepository.findByAppId(appId);
assertThat(accessKeyList).hasSize(1);
assertThat(accessKeyList.get(0).getAppId()).isEqualTo(appId);
assertThat(accessKeyList.get(0).getSecret()).isEqualTo("someSecret");
}
@Test
@Sql(scripts = "/sql/accesskey-test.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
public void testFindFirst500ByDataChangeLastModifiedTimeGreaterThanOrderByDataChangeLastModifiedTime() {
Instant instant = LocalDateTime.of(2019, 12, 19, 13, 44, 20)
.atZone(ZoneId.systemDefault())
.toInstant();
Date date = Date.from(instant);
List<AccessKey> accessKeyList = accessKeyRepository
.findFirst500ByDataChangeLastModifiedTimeGreaterThanOrderByDataChangeLastModifiedTimeAsc(date);
assertThat(accessKeyList).hasSize(2);
assertThat(accessKeyList.get(0).getAppId()).isEqualTo("100004458");
assertThat(accessKeyList.get(0).getSecret()).isEqualTo("4003c4d7783443dc9870932bebf3b7fe");
assertThat(accessKeyList.get(1).getAppId()).isEqualTo("100004458");
assertThat(accessKeyList.get(1).getSecret()).isEqualTo("c715cbc80fc44171b43732c3119c9456");
}
}
\ No newline at end of file
package com.ctrip.framework.apollo.biz.service;
import static org.junit.Assert.assertNotNull;
import com.ctrip.framework.apollo.biz.AbstractIntegrationTest;
import com.ctrip.framework.apollo.biz.entity.AccessKey;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author nisiyong
*/
public class AccessKeyServiceTest extends AbstractIntegrationTest {
@Autowired
private AccessKeyService accessKeyService;
@Test
public void testCreate() {
String appId = "someAppId";
String secret = "someSecret";
AccessKey entity = assembleAccessKey(appId, secret);
AccessKey accessKey = accessKeyService.create(appId, entity);
assertNotNull(accessKey);
}
@Test(expected = BadRequestException.class)
public void testCreateWithException() {
String appId = "someAppId";
String secret = "someSecret";
int maxCount = 5;
for (int i = 0; i <= maxCount; i++) {
AccessKey entity = assembleAccessKey(appId, secret);
accessKeyService.create(appId, entity);
}
}
private AccessKey assembleAccessKey(String appId, String secret) {
AccessKey accessKey = new AccessKey();
accessKey.setAppId(appId);
accessKey.setSecret(secret);
return accessKey;
}
}
\ No newline at end of file
INSERT INTO `AccessKey` (`Id`, `AppId`, `Secret`, `IsEnabled`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_CreatedTime`, `DataChange_LastModifiedBy`, `DataChange_LastTime`)
VALUES
(1, 'someAppId', 'someSecret', 0, 0, 'apollo', '2019-12-19 10:28:40', 'apollo', '2019-12-19 10:28:40'),
(2, '100004458', 'c715cbc80fc44171b43732c3119c9456', 0, 0, 'apollo', '2019-12-19 10:39:54', 'apollo', '2019-12-19 14:46:35'),
(3, '100004458', '25a0e68d2a3941edb1ed3ab6dd0646cd', 0, 1, 'apollo', '2019-12-19 13:44:13', 'apollo', '2019-12-19 13:44:19'),
(4, '100004458', '4003c4d7783443dc9870932bebf3b7fe', 0, 0, 'apollo', '2019-12-19 13:43:52', 'apollo', '2019-12-19 13:44:21');
DELETE FROM AccessKey;
DELETE FROM App;
DELETE FROM AppNamespace;
DELETE FROM Cluster;
......
package com.ctrip.framework.apollo.internals;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.RateLimiter;
import com.google.gson.Gson;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification;
......@@ -21,7 +8,9 @@ import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.schedule.ExponentialSchedulePolicy;
import com.ctrip.framework.apollo.core.schedule.SchedulePolicy;
import com.ctrip.framework.apollo.core.signature.Signature;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.tracer.spi.Transaction;
......@@ -30,10 +19,18 @@ import com.ctrip.framework.apollo.util.ExceptionUtil;
import com.ctrip.framework.apollo.util.http.HttpRequest;
import com.ctrip.framework.apollo.util.http.HttpResponse;
import com.ctrip.framework.apollo.util.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.RateLimiter;
import com.google.gson.Gson;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
......@@ -43,6 +40,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -109,6 +108,7 @@ public class RemoteConfigLongPollService {
final String appId = m_configUtil.getAppId();
final String cluster = m_configUtil.getCluster();
final String dataCenter = m_configUtil.getDataCenter();
final String secret = m_configUtil.getAccessKeySecret();
final long longPollingInitialDelayInMills = m_configUtil.getLongPollingInitialDelayInMills();
m_longPollingService.submit(new Runnable() {
@Override
......@@ -121,7 +121,7 @@ public class RemoteConfigLongPollService {
//ignore
}
}
doLongPollingRefresh(appId, cluster, dataCenter);
doLongPollingRefresh(appId, cluster, dataCenter, secret);
}
});
} catch (Throwable ex) {
......@@ -137,7 +137,7 @@ public class RemoteConfigLongPollService {
this.m_longPollingStopped.compareAndSet(false, true);
}
private void doLongPollingRefresh(String appId, String cluster, String dataCenter) {
private void doLongPollingRefresh(String appId, String cluster, String dataCenter, String secret) {
final Random random = new Random();
ServiceDTO lastServiceDto = null;
while (!m_longPollingStopped.get() && !Thread.currentThread().isInterrupted()) {
......@@ -161,8 +161,13 @@ public class RemoteConfigLongPollService {
m_notifications);
logger.debug("Long polling from {}", url);
HttpRequest request = new HttpRequest(url);
request.setReadTimeout(LONG_POLLING_READ_TIMEOUT);
if (!StringUtils.isBlank(secret)) {
Map<String, String> headers = Signature.buildHttpHeaders(url, appId, secret);
request.setHeaders(headers);
}
transaction.addData("Url", url);
......
package com.ctrip.framework.apollo.internals;
import com.ctrip.framework.apollo.enums.ConfigSourceType;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ctrip.framework.apollo.Apollo;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.core.ConfigConsts;
......@@ -22,7 +8,10 @@ import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.core.schedule.ExponentialSchedulePolicy;
import com.ctrip.framework.apollo.core.schedule.SchedulePolicy;
import com.ctrip.framework.apollo.core.signature.Signature;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.enums.ConfigSourceType;
import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
import com.ctrip.framework.apollo.exceptions.ApolloConfigStatusCodeException;
import com.ctrip.framework.apollo.tracer.Tracer;
......@@ -40,6 +29,17 @@ import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import com.google.common.util.concurrent.RateLimiter;
import com.google.gson.Gson;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -174,6 +174,7 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
String appId = m_configUtil.getAppId();
String cluster = m_configUtil.getCluster();
String dataCenter = m_configUtil.getDataCenter();
String secret = m_configUtil.getAccessKeySecret();
Tracer.logEvent("Apollo.Client.ConfigMeta", STRING_JOINER.join(appId, cluster, m_namespace));
int maxRetries = m_configNeedForceRefresh.get() ? 2 : 1;
long onErrorSleepTime = 0; // 0 means no sleep
......@@ -206,7 +207,12 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
dataCenter, m_remoteMessages.get(), m_configCache.get());
logger.debug("Loading config from {}", url);
HttpRequest request = new HttpRequest(url);
if (!StringUtils.isBlank(secret)) {
Map<String, String> headers = Signature.buildHttpHeaders(url, appId, secret);
request.setHeaders(headers);
}
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "queryConfig");
transaction.addData("Url", url);
......
......@@ -61,7 +61,7 @@ public class ApolloApplicationContextInitializer implements
private static final Logger logger = LoggerFactory.getLogger(ApolloApplicationContextInitializer.class);
private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
private static final String[] APOLLO_SYSTEM_PROPERTIES = {"app.id", ConfigConsts.APOLLO_CLUSTER_KEY,
"apollo.cacheDir", ConfigConsts.APOLLO_META_KEY};
"apollo.cacheDir", "apollo.accesskey.secret", ConfigConsts.APOLLO_META_KEY};
private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector
.getInstance(ConfigPropertySourceFactory.class);
......
......@@ -66,6 +66,15 @@ public class ConfigUtil {
return appId;
}
/**
* Get the access key secret for the current application.
*
* @return the current access key secret, null if there is no such secret.
*/
public String getAccessKeySecret() {
return Foundation.app().getAccessKeySecret();
}
/**
* Get the data center info for the current application.
*
......
package com.ctrip.framework.apollo.util.http;
import java.util.Map;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class HttpRequest {
private String m_url;
private Map<String, String> headers;
private int m_connectTimeout;
private int m_readTimeout;
......@@ -22,6 +25,14 @@ public class HttpRequest {
return m_url;
}
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public int getConnectTimeout() {
return m_connectTimeout;
}
......
......@@ -14,6 +14,7 @@ import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* @author Jason Song(song_s@ctrip.com)
......@@ -78,6 +79,13 @@ public class HttpUtil {
conn.setRequestMethod("GET");
Map<String, String> headers = httpRequest.getHeaders();
if (headers != null && headers.size() > 0) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
conn.setRequestProperty(entry.getKey(), entry.getValue());
}
}
int connectTimeout = httpRequest.getConnectTimeout();
if (connectTimeout < 0) {
connectTimeout = m_configUtil.getConnectTimeout();
......
package com.ctrip.framework.apollo.internals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
......@@ -11,14 +12,24 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.build.MockInjector;
import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.core.signature.Signature;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.ctrip.framework.apollo.util.http.HttpRequest;
import com.ctrip.framework.apollo.util.http.HttpResponse;
import com.ctrip.framework.apollo.util.http.HttpUtil;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.SettableFuture;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -29,18 +40,6 @@ import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.test.util.ReflectionTestUtils;
import com.ctrip.framework.apollo.build.MockInjector;
import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.ctrip.framework.apollo.util.http.HttpRequest;
import com.ctrip.framework.apollo.util.http.HttpResponse;
import com.ctrip.framework.apollo.util.http.HttpUtil;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.SettableFuture;
/**
* @author Jason Song(song_s@ctrip.com)
*/
......@@ -58,6 +57,7 @@ public class RemoteConfigLongPollServiceTest {
private static String someServerUrl;
private static String someAppId;
private static String someCluster;
private static String someSecret;
@Before
public void setUp() throws Exception {
......@@ -178,6 +178,59 @@ public class RemoteConfigLongPollServiceTest {
assertEquals(anotherNotificationId, captured.get(anotherKey).longValue());
}
@Test
public void testSubmitLongPollNamespaceWithAccessKeySecret() throws Exception {
someSecret = "someSecret";
RemoteConfigRepository someRepository = mock(RemoteConfigRepository.class);
final String someNamespace = "someNamespace";
ApolloNotificationMessages notificationMessages = new ApolloNotificationMessages();
String someKey = "someKey";
long someNotificationId = 1;
notificationMessages.put(someKey, someNotificationId);
ApolloConfigNotification someNotification = mock(ApolloConfigNotification.class);
when(someNotification.getNamespaceName()).thenReturn(someNamespace);
when(someNotification.getMessages()).thenReturn(notificationMessages);
when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_OK);
when(pollResponse.getBody()).thenReturn(Lists.newArrayList(someNotification));
doAnswer(new Answer<HttpResponse<List<ApolloConfigNotification>>>() {
@Override
public HttpResponse<List<ApolloConfigNotification>> answer(InvocationOnMock invocation)
throws Throwable {
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
}
HttpRequest request = invocation.getArgumentAt(0, HttpRequest.class);
Map<String, String> headers = request.getHeaders();
assertNotNull(headers);
assertTrue(headers.containsKey(Signature.HTTP_HEADER_TIMESTAMP));
assertTrue(headers.containsKey(Signature.HTTP_HEADER_AUTHORIZATION));
return pollResponse;
}
}).when(httpUtil).doGet(any(HttpRequest.class), eq(responseType));
final SettableFuture<Boolean> onNotified = SettableFuture.create();
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
onNotified.set(true);
return null;
}
}).when(someRepository).onLongPollNotified(any(ServiceDTO.class), any(ApolloNotificationMessages.class));
remoteConfigLongPollService.submit(someNamespace, someRepository);
onNotified.get(5000, TimeUnit.MILLISECONDS);
remoteConfigLongPollService.stopLongPollingRefresh();
verify(someRepository, times(1)).onLongPollNotified(any(ServiceDTO.class), any(ApolloNotificationMessages.class));
}
@Test
public void testSubmitLongPollMultipleNamespaces() throws Exception {
RemoteConfigRepository someRepository = mock(RemoteConfigRepository.class);
......@@ -476,6 +529,11 @@ public class RemoteConfigLongPollServiceTest {
return someCluster;
}
@Override
public String getAccessKeySecret() {
return someSecret;
}
@Override
public String getDataCenter() {
return null;
......
package com.ctrip.framework.apollo.internals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
......@@ -12,29 +13,13 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.enums.ConfigSourceType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import com.ctrip.framework.apollo.build.MockInjector;
import com.ctrip.framework.apollo.core.dto.ApolloConfig;
import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification;
import com.ctrip.framework.apollo.core.dto.ApolloNotificationMessages;
import com.ctrip.framework.apollo.core.dto.ServiceDTO;
import com.ctrip.framework.apollo.core.signature.Signature;
import com.ctrip.framework.apollo.enums.ConfigSourceType;
import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.ctrip.framework.apollo.util.http.HttpRequest;
......@@ -46,6 +31,20 @@ import com.google.common.collect.Maps;
import com.google.common.net.UrlEscapers;
import com.google.common.util.concurrent.SettableFuture;
import com.google.gson.Gson;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
/**
* Created by Jason on 4/9/16.
......@@ -64,6 +63,10 @@ public class RemoteConfigRepositoryTest {
private static HttpResponse<List<ApolloConfigNotification>> pollResponse;
private RemoteConfigLongPollService remoteConfigLongPollService;
private static String someAppId;
private static String someCluster;
private static String someSecret;
@Before
public void setUp() throws Exception {
someNamespace = "someName";
......@@ -88,6 +91,9 @@ public class RemoteConfigRepositoryTest {
remoteConfigLongPollService = new RemoteConfigLongPollService();
MockInjector.setInstance(RemoteConfigLongPollService.class, remoteConfigLongPollService);
someAppId = "someAppId";
someCluster = "someCluster";
}
@Test
......@@ -110,6 +116,39 @@ public class RemoteConfigRepositoryTest {
remoteConfigLongPollService.stopLongPollingRefresh();
}
@Test
public void testLoadConfigWithAccessKeySecret() throws Exception {
someSecret = "someSecret";
String someKey = "someKey";
String someValue = "someValue";
Map<String, String> configurations = Maps.newHashMap();
configurations.put(someKey, someValue);
ApolloConfig someApolloConfig = assembleApolloConfig(configurations);
when(someResponse.getStatusCode()).thenReturn(200);
when(someResponse.getBody()).thenReturn(someApolloConfig);
doAnswer(new Answer<HttpResponse<ApolloConfig>>() {
@Override
public HttpResponse<ApolloConfig> answer(InvocationOnMock invocation) throws Throwable {
HttpRequest request = invocation.getArgumentAt(0, HttpRequest.class);
Map<String, String> headers = request.getHeaders();
assertNotNull(headers);
assertTrue(headers.containsKey(Signature.HTTP_HEADER_TIMESTAMP));
assertTrue(headers.containsKey(Signature.HTTP_HEADER_AUTHORIZATION));
return someResponse;
}
}).when(httpUtil).doGet(any(HttpRequest.class), any(Class.class));
RemoteConfigRepository remoteConfigRepository = new RemoteConfigRepository(someNamespace);
Properties config = remoteConfigRepository.getConfig();
assertEquals(configurations, config);
assertEquals(ConfigSourceType.REMOTE, remoteConfigRepository.getSourceType());
remoteConfigLongPollService.stopLongPollingRefresh();
}
@Test(expected = ApolloConfigException.class)
public void testGetRemoteConfigWithServerError() throws Exception {
......@@ -254,12 +293,17 @@ public class RemoteConfigRepositoryTest {
public static class MockConfigUtil extends ConfigUtil {
@Override
public String getAppId() {
return "someApp";
return someAppId;
}
@Override
public String getCluster() {
return "someCluster";
return someCluster;
}
@Override
public String getAccessKeySecret() {
return someSecret;
}
@Override
......
package com.ctrip.framework.apollo.common.dto;
public class AccessKeyDTO extends BaseDTO {
private Long id;
private String secret;
private String appId;
private Boolean enabled;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}
......@@ -6,10 +6,13 @@ import com.ctrip.framework.apollo.biz.message.ReleaseMessageScanner;
import com.ctrip.framework.apollo.configservice.controller.ConfigFileController;
import com.ctrip.framework.apollo.configservice.controller.NotificationController;
import com.ctrip.framework.apollo.configservice.controller.NotificationControllerV2;
import com.ctrip.framework.apollo.configservice.filter.ClientAuthenticationFilter;
import com.ctrip.framework.apollo.configservice.service.ReleaseMessageServiceWithCache;
import com.ctrip.framework.apollo.configservice.service.config.ConfigService;
import com.ctrip.framework.apollo.configservice.service.config.ConfigServiceWithCache;
import com.ctrip.framework.apollo.configservice.service.config.DefaultConfigService;
import com.ctrip.framework.apollo.configservice.util.AccessKeyUtil;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
......@@ -44,6 +47,18 @@ public class ConfigServiceAutoConfiguration {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
@Bean
public FilterRegistrationBean clientAuthenticationFilter(AccessKeyUtil accessKeyUtil) {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new ClientAuthenticationFilter(accessKeyUtil));
filterRegistrationBean.addUrlPatterns("/configs/*");
filterRegistrationBean.addUrlPatterns("/configfiles/*");
filterRegistrationBean.addUrlPatterns("/notifications/v2/*");
return filterRegistrationBean;
}
@Configuration
static class MessageScannerConfiguration {
private final NotificationController notificationController;
......
package com.ctrip.framework.apollo.configservice.filter;
import com.ctrip.framework.apollo.configservice.util.AccessKeyUtil;
import com.ctrip.framework.apollo.core.signature.Signature;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
/**
* @author nisiyong
*/
public class ClientAuthenticationFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(ClientAuthenticationFilter.class);
private static final Long TIMESTAMP_INTERVAL = 60 * 1000L;
private final AccessKeyUtil accessKeyUtil;
public ClientAuthenticationFilter(AccessKeyUtil accessKeyUtil) {
this.accessKeyUtil = accessKeyUtil;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//nothing
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String appId = accessKeyUtil.extractAppIdFromRequest(request);
if (StringUtils.isBlank(appId)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "InvalidAppId");
return;
}
List<String> availableSecrets = accessKeyUtil.findAvailableSecret(appId);
if (!CollectionUtils.isEmpty(availableSecrets)) {
String timestamp = request.getHeader(Signature.HTTP_HEADER_TIMESTAMP);
String authorization = request.getHeader(Signature.HTTP_HEADER_AUTHORIZATION);
// check timestamp, valid within 1 minute
if (!checkTimestamp(timestamp)) {
logger.warn("Invalid timestamp. appId={},timestamp={}", appId, timestamp);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "RequestTimeTooSkewed");
return;
}
// check signature
String path = request.getServletPath();
String query = request.getQueryString();
if (!checkAuthorization(authorization, availableSecrets, timestamp, path, query)) {
logger.warn("Invalid authorization. appId={},authorization={}", appId, authorization);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
//nothing
}
private boolean checkTimestamp(String timestamp) {
long requestTimeMillis = 0L;
try {
requestTimeMillis = Long.parseLong(timestamp);
} catch (NumberFormatException e) {
// nothing to do
}
long x = System.currentTimeMillis() - requestTimeMillis;
return x <= TIMESTAMP_INTERVAL;
}
private boolean checkAuthorization(String authorization, List<String> availableSecrets,
String timestamp, String path, String query) {
String signature = null;
if (authorization != null) {
String[] split = authorization.split(":");
if (split.length > 1) {
signature = split[1];
}
}
for (String secret : availableSecrets) {
String availableSignature = accessKeyUtil.buildSignature(path, query, timestamp, secret);
if (Objects.equals(signature, availableSignature)) {
return true;
}
}
return false;
}
}
package com.ctrip.framework.apollo.configservice.service;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.entity.AccessKey;
import com.ctrip.framework.apollo.biz.repository.AccessKeyRepository;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.tracer.spi.Transaction;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.MultimapBuilder.ListMultimapBuilder;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
/**
* @author nisiyong
*/
@Service
public class AccessKeyServiceWithCache implements InitializingBean {
private static Logger logger = LoggerFactory.getLogger(AccessKeyServiceWithCache.class);
private final AccessKeyRepository accessKeyRepository;
private final BizConfig bizConfig;
private int scanInterval;
private TimeUnit scanIntervalTimeUnit;
private int rebuildInterval;
private TimeUnit rebuildIntervalTimeUnit;
private ScheduledExecutorService scheduledExecutorService;
private Date lastTimeScanned;
private ListMultimap<String, AccessKey> accessKeyCache;
private ConcurrentMap<Long, AccessKey> accessKeyIdCache;
@Autowired
public AccessKeyServiceWithCache(AccessKeyRepository accessKeyRepository, BizConfig bizConfig) {
this.accessKeyRepository = accessKeyRepository;
this.bizConfig = bizConfig;
initialize();
}
private void initialize() {
scheduledExecutorService = new ScheduledThreadPoolExecutor(1,
ApolloThreadFactory.create("AccessKeyServiceWithCache", true));
lastTimeScanned = new Date(0L);
ListMultimap<String, AccessKey> multimap = ListMultimapBuilder.hashKeys(128)
.arrayListValues().build();
accessKeyCache = Multimaps.synchronizedListMultimap(multimap);
accessKeyIdCache = Maps.newConcurrentMap();
}
public List<String> getAvailableSecrets(String appId) {
List<AccessKey> accessKeys = accessKeyCache.get(appId);
if (CollectionUtils.isEmpty(accessKeys)) {
return Collections.emptyList();
}
return accessKeys.stream()
.filter(AccessKey::isEnabled)
.map(AccessKey::getSecret)
.collect(Collectors.toList());
}
@Override
public void afterPropertiesSet() throws Exception {
populateDataBaseInterval();
scanNewAndUpdatedAccessKeys(); //block the startup process until load finished
scheduledExecutorService.scheduleWithFixedDelay(this::scanNewAndUpdatedAccessKeys,
scanInterval, scanInterval, scanIntervalTimeUnit);
scheduledExecutorService.scheduleAtFixedRate(this::rebuildAccessKeyCache,
rebuildInterval, rebuildInterval, rebuildIntervalTimeUnit);
}
private void scanNewAndUpdatedAccessKeys() {
Transaction transaction = Tracer.newTransaction("Apollo.AccessKeyServiceWithCache",
"scanNewAndUpdatedAccessKeys");
try {
loadNewAndUpdatedAccessKeys();
transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
transaction.setStatus(ex);
logger.error("Load new/updated app access keys failed", ex);
} finally {
transaction.complete();
}
}
private void rebuildAccessKeyCache() {
Transaction transaction = Tracer.newTransaction("Apollo.AccessKeyServiceWithCache",
"rebuildCache");
try {
deleteAccessKeyCache();
transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
transaction.setStatus(ex);
logger.error("Rebuild cache failed", ex);
} finally {
transaction.complete();
}
}
private void loadNewAndUpdatedAccessKeys() {
boolean hasMore = true;
while (hasMore && !Thread.currentThread().isInterrupted()) {
//current batch is 500
List<AccessKey> accessKeys = accessKeyRepository
.findFirst500ByDataChangeLastModifiedTimeGreaterThanOrderByDataChangeLastModifiedTimeAsc(lastTimeScanned);
if (CollectionUtils.isEmpty(accessKeys)) {
break;
}
int scanned = accessKeys.size();
mergeAccessKeys(accessKeys);
logger.info("Loaded {} new/updated Accesskey from startTime {}", scanned, lastTimeScanned);
hasMore = scanned == 500;
lastTimeScanned = accessKeys.get(scanned - 1).getDataChangeLastModifiedTime();
// In order to avoid missing some records at the last time, we need to scan records at this time individually
if (hasMore) {
List<AccessKey> lastModifiedTimeAccessKeys = accessKeyRepository.findByDataChangeLastModifiedTime(lastTimeScanned);
mergeAccessKeys(lastModifiedTimeAccessKeys);
logger.info("Loaded {} new/updated Accesskey at lastModifiedTime {}", scanned, lastTimeScanned);
}
}
}
private void mergeAccessKeys(List<AccessKey> accessKeys) {
for (AccessKey accessKey : accessKeys) {
AccessKey thatInCache = accessKeyIdCache.get(accessKey.getId());
accessKeyIdCache.put(accessKey.getId(), accessKey);
accessKeyCache.put(accessKey.getAppId(), accessKey);
if (thatInCache != null && accessKey.getDataChangeLastModifiedTime()
.after(thatInCache.getDataChangeLastModifiedTime())) {
accessKeyCache.remove(accessKey.getAppId(), thatInCache);
logger.info("Found Accesskey changes, old: {}, new: {}", thatInCache, accessKey);
}
}
}
private void deleteAccessKeyCache() {
List<Long> ids = Lists.newArrayList(accessKeyIdCache.keySet());
if (CollectionUtils.isEmpty(ids)) {
return;
}
List<List<Long>> partitionIds = Lists.partition(ids, 500);
for (List<Long> toRebuildIds : partitionIds) {
Iterable<AccessKey> accessKeys = accessKeyRepository.findAllById(toRebuildIds);
Set<Long> foundIds = Sets.newHashSet();
for (AccessKey accessKey : accessKeys) {
foundIds.add(accessKey.getId());
}
//handle deleted
SetView<Long> deletedIds = Sets.difference(Sets.newHashSet(toRebuildIds), foundIds);
handleDeletedAccessKeys(deletedIds);
}
}
private void handleDeletedAccessKeys(Set<Long> deletedIds) {
if (CollectionUtils.isEmpty(deletedIds)) {
return;
}
for (Long deletedId : deletedIds) {
AccessKey deleted = accessKeyIdCache.remove(deletedId);
if (deleted == null) {
continue;
}
accessKeyCache.remove(deleted.getAppId(), deleted);
logger.info("Found AccessKey deleted, {}", deleted);
}
}
private void populateDataBaseInterval() {
scanInterval = bizConfig.accessKeyCacheScanInterval();
scanIntervalTimeUnit = bizConfig.accessKeyCacheScanIntervalTimeUnit();
rebuildInterval = bizConfig.accessKeyCacheRebuildInterval();
rebuildIntervalTimeUnit = bizConfig.accessKeyCacheRebuildIntervalTimeUnit();
}
}
package com.ctrip.framework.apollo.configservice.util;
import com.ctrip.framework.apollo.configservice.service.AccessKeyServiceWithCache;
import com.ctrip.framework.apollo.core.signature.Signature;
import com.google.common.base.Strings;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
/**
* @author nisiyong
*/
@Component
public class AccessKeyUtil {
private static final String URL_SEPARATOR = "/";
private static final String URL_CONFIGS_PREFIX = "/configs/";
private static final String URL_CONFIGFILES_JSON_PREFIX = "/configfiles/json/";
private static final String URL_CONFIGFILES_PREFIX = "/configfiles/";
private static final String URL_NOTIFICATIONS_PREFIX = "/notifications/v2";
private final AccessKeyServiceWithCache accessKeyServiceWithCache;
public AccessKeyUtil(AccessKeyServiceWithCache accessKeyServiceWithCache) {
this.accessKeyServiceWithCache = accessKeyServiceWithCache;
}
public List<String> findAvailableSecret(String appId) {
return accessKeyServiceWithCache.getAvailableSecrets(appId);
}
public String extractAppIdFromRequest(HttpServletRequest request) {
String appId = null;
String servletPath = request.getServletPath();
if (StringUtils.startsWith(servletPath, URL_CONFIGS_PREFIX)) {
appId = StringUtils.substringBetween(servletPath, URL_CONFIGS_PREFIX, URL_SEPARATOR);
} else if (StringUtils.startsWith(servletPath, URL_CONFIGFILES_JSON_PREFIX)) {
appId = StringUtils.substringBetween(servletPath, URL_CONFIGFILES_JSON_PREFIX, URL_SEPARATOR);
} else if (StringUtils.startsWith(servletPath, URL_CONFIGFILES_PREFIX)) {
appId = StringUtils.substringBetween(servletPath, URL_CONFIGFILES_PREFIX, URL_SEPARATOR);
} else if (StringUtils.startsWith(servletPath, URL_NOTIFICATIONS_PREFIX)) {
appId = request.getParameter("appId");
}
return appId;
}
public String buildSignature(String path, String query, String timestampString, String secret) {
String pathWithQuery = path;
if (!Strings.isNullOrEmpty(query)) {
pathWithQuery += "?" + query;
}
return Signature.signature(timestampString, pathWithQuery, secret);
}
}
package com.ctrip.framework.apollo.configservice.filter;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.configservice.util.AccessKeyUtil;
import com.ctrip.framework.apollo.core.signature.Signature;
import com.google.common.collect.Lists;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
/**
* @author nisiyong
*/
@RunWith(MockitoJUnitRunner.class)
public class ClientAuthenticationFilterTest {
private ClientAuthenticationFilter clientAuthenticationFilter;
@Mock
private AccessKeyUtil accessKeyUtil;
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
@Mock
private FilterChain filterChain;
@Before
public void setUp() {
clientAuthenticationFilter = new ClientAuthenticationFilter(accessKeyUtil);
}
@Test
public void testInvalidAppId() throws Exception {
when(accessKeyUtil.extractAppIdFromRequest(any())).thenReturn(null);
clientAuthenticationFilter.doFilter(request, response, filterChain);
verify(response).sendError(HttpServletResponse.SC_BAD_REQUEST, "InvalidAppId");
verify(filterChain, never()).doFilter(request, response);
}
@Test
public void testRequestTimeTooSkewed() throws Exception {
String appId = "someAppId";
List<String> secrets = Lists.newArrayList("someSecret");
String oneMinAgoTimestamp = Long.toString(System.currentTimeMillis() - 61 * 1000);
when(accessKeyUtil.extractAppIdFromRequest(any())).thenReturn(appId);
when(accessKeyUtil.findAvailableSecret(appId)).thenReturn(secrets);
when(request.getHeader(Signature.HTTP_HEADER_TIMESTAMP)).thenReturn(oneMinAgoTimestamp);
clientAuthenticationFilter.doFilter(request, response, filterChain);
verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED, "RequestTimeTooSkewed");
verify(filterChain, never()).doFilter(request, response);
}
@Test
public void testUnauthorized() throws Exception {
String appId = "someAppId";
String availableSignature = "someSignature";
List<String> secrets = Lists.newArrayList("someSecret");
String oneMinAgoTimestamp = Long.toString(System.currentTimeMillis());
String errorAuthorization = "Apollo someAppId:wrongSignature";
when(accessKeyUtil.extractAppIdFromRequest(any())).thenReturn(appId);
when(accessKeyUtil.findAvailableSecret(appId)).thenReturn(secrets);
when(accessKeyUtil.buildSignature(any(), any(), any(), any())).thenReturn(availableSignature);
when(request.getHeader(Signature.HTTP_HEADER_TIMESTAMP)).thenReturn(oneMinAgoTimestamp);
when(request.getHeader(Signature.HTTP_HEADER_AUTHORIZATION)).thenReturn(errorAuthorization);
clientAuthenticationFilter.doFilter(request, response, filterChain);
verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
verify(filterChain, never()).doFilter(request, response);
}
@Test
public void testAuthorizedSuccessfully() throws Exception {
String appId = "someAppId";
String availableSignature = "someSignature";
List<String> secrets = Lists.newArrayList("someSecret");
String oneMinAgoTimestamp = Long.toString(System.currentTimeMillis());
String correctAuthorization = "Apollo someAppId:someSignature";
when(accessKeyUtil.extractAppIdFromRequest(any())).thenReturn(appId);
when(accessKeyUtil.findAvailableSecret(appId)).thenReturn(secrets);
when(accessKeyUtil.buildSignature(any(), any(), any(), any())).thenReturn(availableSignature);
when(request.getHeader(Signature.HTTP_HEADER_TIMESTAMP)).thenReturn(oneMinAgoTimestamp);
when(request.getHeader(Signature.HTTP_HEADER_AUTHORIZATION)).thenReturn(correctAuthorization);
clientAuthenticationFilter.doFilter(request, response, filterChain);
verify(response, never()).sendError(HttpServletResponse.SC_BAD_REQUEST, "InvalidAppId");
verify(response, never()).sendError(HttpServletResponse.SC_UNAUTHORIZED, "RequestTimeTooSkewed");
verify(response, never()).sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
verify(filterChain, times(1)).doFilter(request, response);
}
}
\ No newline at end of file
package com.ctrip.framework.apollo.configservice.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.entity.AccessKey;
import com.ctrip.framework.apollo.biz.repository.AccessKeyRepository;
import com.google.common.collect.Lists;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
/**
* @author nisiyong
*/
@RunWith(MockitoJUnitRunner.class)
public class AccessKeyServiceWithCacheTest {
private AccessKeyServiceWithCache accessKeyServiceWithCache;
@Mock
private AccessKeyRepository accessKeyRepository;
@Mock
private BizConfig bizConfig;
private int scanInterval;
private TimeUnit scanIntervalTimeUnit;
@Before
public void setUp() {
accessKeyServiceWithCache = new AccessKeyServiceWithCache(accessKeyRepository, bizConfig);
scanInterval = 50;
scanIntervalTimeUnit = TimeUnit.MILLISECONDS;
when(bizConfig.accessKeyCacheScanInterval()).thenReturn(scanInterval);
when(bizConfig.accessKeyCacheScanIntervalTimeUnit()).thenReturn(scanIntervalTimeUnit);
when(bizConfig.accessKeyCacheRebuildInterval()).thenReturn(scanInterval);
when(bizConfig.accessKeyCacheRebuildIntervalTimeUnit()).thenReturn(scanIntervalTimeUnit);
}
@Test
public void testGetAvailableSecrets() throws Exception {
String appId = "someAppId";
AccessKey firstAccessKey = assembleAccessKey(1L, appId, "secret-1", false,
false, 1577808000000L);
AccessKey secondAccessKey = assembleAccessKey(2L, appId, "secret-2", false,
false, 1577808001000L);
AccessKey thirdAccessKey = assembleAccessKey(3L, appId, "secret-3", true,
false, 1577808005000L);
// Initialize
accessKeyServiceWithCache.afterPropertiesSet();
assertThat(accessKeyServiceWithCache.getAvailableSecrets(appId)).isEmpty();
// Add access key, disable by default
when(accessKeyRepository.findFirst500ByDataChangeLastModifiedTimeGreaterThanOrderByDataChangeLastModifiedTimeAsc(new Date(0L)))
.thenReturn(Lists.newArrayList(firstAccessKey, secondAccessKey));
when(accessKeyRepository.findAllById(anyList()))
.thenReturn(Lists.newArrayList(firstAccessKey, secondAccessKey));
TimeUnit.SECONDS.sleep(1);
assertThat(accessKeyServiceWithCache.getAvailableSecrets(appId)).isEmpty();
// Update access key, enable both of them
firstAccessKey = assembleAccessKey(1L, appId, "secret-1", true, false, 1577808002000L);
secondAccessKey = assembleAccessKey(2L, appId, "secret-2", true, false, 1577808003000L);
when(accessKeyRepository.findFirst500ByDataChangeLastModifiedTimeGreaterThanOrderByDataChangeLastModifiedTimeAsc(new Date(1577808001000L)))
.thenReturn(Lists.newArrayList(firstAccessKey, secondAccessKey));
when(accessKeyRepository.findAllById(anyList()))
.thenReturn(Lists.newArrayList(firstAccessKey, secondAccessKey));
TimeUnit.SECONDS.sleep(1);
assertThat(accessKeyServiceWithCache.getAvailableSecrets(appId)).containsExactly("secret-1", "secret-2");
// Update access key, disable the first one
firstAccessKey = assembleAccessKey(1L, appId, "secret-1", false, false, 1577808004000L);
when(accessKeyRepository.findFirst500ByDataChangeLastModifiedTimeGreaterThanOrderByDataChangeLastModifiedTimeAsc(new Date(1577808003000L)))
.thenReturn(Lists.newArrayList(firstAccessKey));
when(accessKeyRepository.findAllById(anyList()))
.thenReturn(Lists.newArrayList(firstAccessKey, secondAccessKey));
TimeUnit.SECONDS.sleep(1);
assertThat(accessKeyServiceWithCache.getAvailableSecrets(appId)).containsExactly("secret-2");
// Delete access key, delete the second one
when(accessKeyRepository.findAllById(anyList()))
.thenReturn(Lists.newArrayList(firstAccessKey));
TimeUnit.SECONDS.sleep(1);
assertThat(accessKeyServiceWithCache.getAvailableSecrets(appId)).isEmpty();
// Add new access key in runtime, enable by default
when(accessKeyRepository.findFirst500ByDataChangeLastModifiedTimeGreaterThanOrderByDataChangeLastModifiedTimeAsc(new Date(1577808004000L)))
.thenReturn(Lists.newArrayList(thirdAccessKey));
when(accessKeyRepository.findAllById(anyList()))
.thenReturn(Lists.newArrayList(firstAccessKey, thirdAccessKey));
TimeUnit.SECONDS.sleep(1);
assertThat(accessKeyServiceWithCache.getAvailableSecrets(appId)).containsExactly("secret-3");
}
public AccessKey assembleAccessKey(Long id, String appId, String secret, boolean enabled,
boolean deleted, long dataChangeLastModifiedTime) {
AccessKey accessKey = new AccessKey();
accessKey.setId(id);
accessKey.setAppId(appId);
accessKey.setSecret(secret);
accessKey.setEnabled(enabled);
accessKey.setDeleted(deleted);
accessKey.setDataChangeLastModifiedTime(new Date(dataChangeLastModifiedTime));
return accessKey;
}
}
\ No newline at end of file
package com.ctrip.framework.apollo.configservice.util;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.configservice.service.AccessKeyServiceWithCache;
import com.google.common.collect.Lists;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
/**
* @author nisiyong
*/
@RunWith(MockitoJUnitRunner.class)
public class AccessKeyUtilTest {
private AccessKeyUtil accessKeyUtil;
@Mock
private AccessKeyServiceWithCache accessKeyServiceWithCache;
@Mock
private HttpServletRequest request;
@Before
public void setUp() {
accessKeyUtil = new AccessKeyUtil(accessKeyServiceWithCache);
}
@Test
public void testFindAvailableSecret() {
String appId = "someAppId";
List<String> returnSecrets = Lists.newArrayList("someSecret");
when(accessKeyServiceWithCache.getAvailableSecrets(appId)).thenReturn(returnSecrets);
List<String> availableSecret = accessKeyUtil.findAvailableSecret(appId);
assertThat(availableSecret).containsExactly("someSecret");
verify(accessKeyServiceWithCache).getAvailableSecrets(appId);
}
@Test
public void testExtractAppIdFromRequest1() {
when(request.getServletPath()).thenReturn("/configs/someAppId/default/application");
String appId = accessKeyUtil.extractAppIdFromRequest(request);
assertThat(appId).isEqualTo("someAppId");
}
@Test
public void testExtractAppIdFromRequest2() {
when(request.getServletPath()).thenReturn("/configfiles/json/someAppId/default/application");
String appId = accessKeyUtil.extractAppIdFromRequest(request);
assertThat(appId).isEqualTo("someAppId");
}
@Test
public void testExtractAppIdFromRequest3() {
when(request.getServletPath()).thenReturn("/configfiles/someAppId/default/application");
String appId = accessKeyUtil.extractAppIdFromRequest(request);
assertThat(appId).isEqualTo("someAppId");
}
@Test
public void testExtractAppIdFromRequest4() {
when(request.getServletPath()).thenReturn("/notifications/v2");
when(request.getParameter("appId")).thenReturn("someAppId");
String appId = accessKeyUtil.extractAppIdFromRequest(request);
assertThat(appId).isEqualTo("someAppId");
}
@Test
public void buildSignature() {
String path = "/configs/someAppId/default/application";
String query = "ip=10.0.0.1";
String timestamp = "1575018989200";
String secret = "someSecret";
String actualSignature = accessKeyUtil.buildSignature(path, query, timestamp, secret);
String expectedSignature = "WYjjyJFei6DYiaMlwZjew2O/Yqk=";
assertThat(actualSignature).isEqualTo(expectedSignature);
}
}
\ No newline at end of file
package com.ctrip.framework.apollo.core.signature;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* @author nisiyong
*/
public class HmacSha1Utils {
private static final String ALGORITHM_NAME = "HmacSHA1";
private static final String ENCODING = "UTF-8";
public static String signString(String stringToSign, String accessKeySecret) {
try {
Mac mac = Mac.getInstance(ALGORITHM_NAME);
mac.init(new SecretKeySpec(
accessKeySecret.getBytes(ENCODING),
ALGORITHM_NAME
));
byte[] signData = mac.doFinal(stringToSign.getBytes(ENCODING));
return DatatypeConverter.printBase64Binary(signData);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException | InvalidKeyException e) {
throw new IllegalArgumentException(e.toString());
}
}
}
\ No newline at end of file
package com.ctrip.framework.apollo.core.signature;
import com.google.common.collect.Maps;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
/**
* @author nisiyong
*/
public class Signature {
/**
* Authorization=Apollo {appId}:{sign}
*/
private static final String AUTHORIZATION_FORMAT = "Apollo %s:%s";
private static final String DELIMITER = "\n";
public static final String HTTP_HEADER_AUTHORIZATION = "Authorization";
public static final String HTTP_HEADER_TIMESTAMP = "Timestamp";
public static String signature(String timestamp, String pathWithQuery, String secret) {
String stringToSign = timestamp + DELIMITER + pathWithQuery;
return HmacSha1Utils.signString(stringToSign, secret);
}
public static Map<String, String> buildHttpHeaders(String url, String appId, String secret) {
long currentTimeMillis = System.currentTimeMillis();
String timestamp = String.valueOf(currentTimeMillis);
String pathWithQuery = url2PathWithQuery(url);
String signature = signature(timestamp, pathWithQuery, secret);
Map<String, String> headers = Maps.newHashMap();
headers.put(HTTP_HEADER_AUTHORIZATION, String.format(AUTHORIZATION_FORMAT, appId, signature));
headers.put(HTTP_HEADER_TIMESTAMP, timestamp);
return headers;
}
private static String url2PathWithQuery(String urlString) {
try {
URL url = new URL(urlString);
String path = url.getPath();
String query = url.getQuery();
String pathWithQuery = path;
if (query != null && query.length() > 0) {
pathWithQuery += "?" + query;
}
return pathWithQuery;
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Invalid url pattern: " + urlString, e);
}
}
}
......@@ -14,16 +14,19 @@ import com.ctrip.framework.foundation.spi.provider.ApplicationProvider;
import com.ctrip.framework.foundation.spi.provider.Provider;
public class DefaultApplicationProvider implements ApplicationProvider {
private static final Logger logger = LoggerFactory.getLogger(DefaultApplicationProvider.class);
public static final String APP_PROPERTIES_CLASSPATH = "/META-INF/app.properties";
private Properties m_appProperties = new Properties();
private String m_appId;
private String accessKeySecret;
@Override
public void initialize() {
try {
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(APP_PROPERTIES_CLASSPATH.substring(1));
InputStream in = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(APP_PROPERTIES_CLASSPATH.substring(1));
if (in == null) {
in = DefaultApplicationProvider.class.getResourceAsStream(APP_PROPERTIES_CLASSPATH);
}
......@@ -39,13 +42,15 @@ public class DefaultApplicationProvider implements ApplicationProvider {
try {
if (in != null) {
try {
m_appProperties.load(new InputStreamReader(new BOMInputStream(in), StandardCharsets.UTF_8));
m_appProperties
.load(new InputStreamReader(new BOMInputStream(in), StandardCharsets.UTF_8));
} finally {
in.close();
}
}
initAppId();
initAccessKey();
} catch (Throwable ex) {
logger.error("Initialize DefaultApplicationProvider failed.", ex);
}
......@@ -56,6 +61,11 @@ public class DefaultApplicationProvider implements ApplicationProvider {
return m_appId;
}
@Override
public String getAccessKeySecret() {
return accessKeySecret;
}
@Override
public boolean isAppIdSet() {
return !Utils.isBlank(m_appId);
......@@ -67,6 +77,12 @@ public class DefaultApplicationProvider implements ApplicationProvider {
String val = getAppId();
return val == null ? defaultValue : val;
}
if ("apollo.accesskey.secret".equals(name)) {
String val = getAccessKeySecret();
return val == null ? defaultValue : val;
}
String val = m_appProperties.getProperty(name, defaultValue);
return val == null ? defaultValue : val;
}
......@@ -97,16 +113,50 @@ public class DefaultApplicationProvider implements ApplicationProvider {
m_appId = m_appProperties.getProperty("app.id");
if (!Utils.isBlank(m_appId)) {
m_appId = m_appId.trim();
logger.info("App ID is set to {} by app.id property from {}", m_appId, APP_PROPERTIES_CLASSPATH);
logger.info("App ID is set to {} by app.id property from {}", m_appId,
APP_PROPERTIES_CLASSPATH);
return;
}
m_appId = null;
logger.warn("app.id is not available from System Property and {}. It is set to null", APP_PROPERTIES_CLASSPATH);
logger.warn("app.id is not available from System Property and {}. It is set to null",
APP_PROPERTIES_CLASSPATH);
}
private void initAccessKey() {
// 1. Get accesskey secret from System Property
accessKeySecret = System.getProperty("apollo.accesskey.secret");
if (!Utils.isBlank(accessKeySecret)) {
accessKeySecret = accessKeySecret.trim();
logger
.info("ACCESSKEY SECRET is set by apollo.accesskey.secret property from System Property");
return;
}
//2. Try to get accesskey secret from OS environment variable
accessKeySecret = System.getenv("APOLLO_ACCESSKEY_SECRET");
if (!Utils.isBlank(accessKeySecret)) {
accessKeySecret = accessKeySecret.trim();
logger.info(
"ACCESSKEY SECRET is set by APOLLO_ACCESSKEY_SECRET property from OS environment variable");
return;
}
// 3. Try to get accesskey secret from app.properties.
accessKeySecret = m_appProperties.getProperty("apollo.accesskey.secret");
if (!Utils.isBlank(accessKeySecret)) {
accessKeySecret = accessKeySecret.trim();
logger.info("ACCESSKEY SECRET is set by apollo.accesskey.secret property from {}",
APP_PROPERTIES_CLASSPATH);
return;
}
accessKeySecret = null;
}
@Override
public String toString() {
return "appId [" + getAppId() + "] properties: " + m_appProperties + " (DefaultApplicationProvider)";
return "appId [" + getAppId() + "] properties: " + m_appProperties
+ " (DefaultApplicationProvider)";
}
}
......@@ -28,6 +28,11 @@ public class NullProvider implements ApplicationProvider, NetworkProvider, Serve
return null;
}
@Override
public String getAccessKeySecret() {
return null;
}
@Override
public boolean isAppIdSet() {
return false;
......
......@@ -11,6 +11,11 @@ public interface ApplicationProvider extends Provider {
*/
public String getAppId();
/**
* @return the application's access key secret
*/
public String getAccessKeySecret();
/**
* @return whether the application's app id is set or not
*/
......
package com.ctrip.framework.apollo.core.signature;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
/**
* @author nisiyong
*/
public class HmacSha1UtilsTest {
@Test
public void testSignString() {
String stringToSign = "1576478257344\n/configs/100004458/default/application?ip=10.0.0.1";
String accessKeySecret = "df23df3f59884980844ff3dada30fa97";
String actualSignature = HmacSha1Utils.signString(stringToSign, accessKeySecret);
String expectedSignature = "EoKyziXvKqzHgwx+ijDJwgVTDgE=";
assertThat(actualSignature).isEqualTo(expectedSignature);
}
}
\ No newline at end of file
package com.ctrip.framework.apollo.core.signature;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Map;
import org.junit.Test;
/**
* @author nisiyong
*/
public class SignatureTest {
@Test
public void testSignature() {
String timestamp = "1576478257344";
String pathWithQuery = "/configs/100004458/default/application?ip=10.0.0.1";
String secret = "df23df3f59884980844ff3dada30fa97";
String actualSignature = Signature.signature(timestamp, pathWithQuery, secret);
String expectedSignature = "EoKyziXvKqzHgwx+ijDJwgVTDgE=";
assertThat(actualSignature).isEqualTo(expectedSignature);
}
@Test
public void testBuildHttpHeaders() {
String url = "http://10.0.0.1:8080/configs/100004458/default/application?ip=10.0.0.1";
String appId = "100004458";
String secret = "df23df3f59884980844ff3dada30fa97";
Map<String, String> actualHttpHeaders = Signature.buildHttpHeaders(url, appId, secret);
assertThat(actualHttpHeaders)
.containsKeys(Signature.HTTP_HEADER_AUTHORIZATION, Signature.HTTP_HEADER_TIMESTAMP);
}
}
\ No newline at end of file
......@@ -6,12 +6,9 @@ import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileInputStream;
import org.junit.Before;
import org.junit.Test;
import com.ctrip.framework.foundation.internals.provider.DefaultApplicationProvider;
public class DefaultApplicationProviderTest {
private DefaultApplicationProvider defaultApplicationProvider;
String PREDEFINED_APP_ID = "110402";
......@@ -43,12 +40,16 @@ public class DefaultApplicationProviderTest {
@Test
public void testLoadAppPropertiesWithSystemProperty() throws Exception {
String someAppId = "someAppId";
String someSecret = "someSecret";
System.setProperty("app.id", someAppId);
System.setProperty("apollo.accesskey.secret", someSecret);
defaultApplicationProvider.initialize();
System.clearProperty("app.id");
System.clearProperty("apollo.accesskey.secret");
assertEquals(someAppId, defaultApplicationProvider.getAppId());
assertTrue(defaultApplicationProvider.isAppIdSet());
assertEquals(someSecret, defaultApplicationProvider.getAccessKeySecret());
}
@Test
......
package com.ctrip.framework.apollo.portal.api;
import com.ctrip.framework.apollo.common.dto.AccessKeyDTO;
import com.ctrip.framework.apollo.common.dto.AppDTO;
import com.ctrip.framework.apollo.common.dto.AppNamespaceDTO;
import com.ctrip.framework.apollo.common.dto.ClusterDTO;
......@@ -230,6 +231,36 @@ public class AdminServiceAPI {
}
}
@Service
public static class AccessKeyAPI extends API {
public AccessKeyDTO create(Env env, AccessKeyDTO accessKey) {
return restTemplate.post(env, "apps/{appId}/accesskeys",
accessKey, AccessKeyDTO.class, accessKey.getAppId());
}
public List<AccessKeyDTO> findByAppId(Env env, String appId) {
AccessKeyDTO[] accessKeys = restTemplate.get(env, "apps/{appId}/accesskeys",
AccessKeyDTO[].class, appId);
return Arrays.asList(accessKeys);
}
public void delete(Env env, String appId, long id, String operator) {
restTemplate.delete(env, "apps/{appId}/accesskeys/{id}?operator={operator}",
appId, id, operator);
}
public void enable(Env env, String appId, long id, String operator) {
restTemplate.put(env, "apps/{appId}/accesskeys/{id}/enable?operator={operator}",
null, appId, id, operator);
}
public void disable(Env env, String appId, long id, String operator) {
restTemplate.put(env, "apps/{appId}/accesskeys/{id}/disable?operator={operator}",
null, appId, id, operator);
}
}
@Service
public static class ReleaseAPI extends API {
......
......@@ -14,6 +14,8 @@ public interface TracerEventType {
String CREATE_CLUSTER = "Cluster.Create";
String CREATE_ACCESS_KEY = "AccessKey.Create";
String CREATE_NAMESPACE = "Namespace.Create";
String API_RETRY = "API.Retry";
......
package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.common.dto.AccessKeyDTO;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.service.AccessKeyService;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import java.util.List;
import java.util.UUID;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @author nisiyong
*/
@RestController
public class AccessKeyController {
private final UserInfoHolder userInfoHolder;
private final AccessKeyService accessKeyService;
public AccessKeyController(
UserInfoHolder userInfoHolder,
AccessKeyService accessKeyService) {
this.userInfoHolder = userInfoHolder;
this.accessKeyService = accessKeyService;
}
@PreAuthorize(value = "@permissionValidator.isAppAdmin(#appId)")
@PostMapping(value = "/apps/{appId}/envs/{env}/accesskeys")
public AccessKeyDTO save(@PathVariable String appId, @PathVariable String env,
@RequestBody AccessKeyDTO accessKeyDTO) {
String secret = UUID.randomUUID().toString().replaceAll("-", "");
accessKeyDTO.setAppId(appId);
accessKeyDTO.setSecret(secret);
return accessKeyService.createAccessKey(Env.fromString(env), accessKeyDTO);
}
@PreAuthorize(value = "@permissionValidator.isAppAdmin(#appId)")
@GetMapping(value = "/apps/{appId}/envs/{env}/accesskeys")
public List<AccessKeyDTO> findByAppId(@PathVariable String appId,
@PathVariable String env) {
return accessKeyService.findByAppId(Env.fromString(env), appId);
}
@PreAuthorize(value = "@permissionValidator.isAppAdmin(#appId)")
@DeleteMapping(value = "/apps/{appId}/envs/{env}/accesskeys/{id}")
public void delete(@PathVariable String appId,
@PathVariable String env,
@PathVariable long id) {
String operator = userInfoHolder.getUser().getUserId();
accessKeyService.deleteAccessKey(Env.fromString(env), appId, id, operator);
}
@PreAuthorize(value = "@permissionValidator.isAppAdmin(#appId)")
@PutMapping(value = "/apps/{appId}/envs/{env}/accesskeys/{id}/enable")
public void enable(@PathVariable String appId,
@PathVariable String env,
@PathVariable long id) {
String operator = userInfoHolder.getUser().getUserId();
accessKeyService.enable(Env.fromString(env), appId, id, operator);
}
@PreAuthorize(value = "@permissionValidator.isAppAdmin(#appId)")
@PutMapping(value = "/apps/{appId}/envs/{env}/accesskeys/{id}/disable")
public void disable(@PathVariable String appId,
@PathVariable String env,
@PathVariable long id) {
String operator = userInfoHolder.getUser().getUserId();
accessKeyService.disable(Env.fromString(env), appId, id, operator);
}
}
package com.ctrip.framework.apollo.portal.service;
import com.ctrip.framework.apollo.common.dto.AccessKeyDTO;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI.AccessKeyAPI;
import com.ctrip.framework.apollo.portal.constant.TracerEventType;
import com.ctrip.framework.apollo.tracer.Tracer;
import java.util.List;
import org.springframework.stereotype.Service;
@Service
public class AccessKeyService {
private final AdminServiceAPI.AccessKeyAPI accessKeyAPI;
public AccessKeyService(AccessKeyAPI accessKeyAPI) {
this.accessKeyAPI = accessKeyAPI;
}
public List<AccessKeyDTO> findByAppId(Env env, String appId) {
return accessKeyAPI.findByAppId(env, appId);
}
public AccessKeyDTO createAccessKey(Env env, AccessKeyDTO accessKey) {
AccessKeyDTO accessKeyDTO = accessKeyAPI.create(env, accessKey);
Tracer.logEvent(TracerEventType.CREATE_ACCESS_KEY, accessKey.getAppId());
return accessKeyDTO;
}
public void deleteAccessKey(Env env, String appId, long id, String operator) {
accessKeyAPI.delete(env, appId, id, operator);
}
public void enable(Env env, String appId, long id, String operator) {
accessKeyAPI.enable(env, appId, id, operator);
}
public void disable(Env env, String appId, long id, String operator) {
accessKeyAPI.disable(env, appId, id, operator);
}
}
......@@ -37,28 +37,32 @@
<div class="panel-body row">
<section class="context" ng-show="hasAssignUserPermission">
<!--project admin-->
<section class="form-horizontal" ng-show="hasManageAppMasterPermission">
<h5>{{'App.Setting.Admin' | translate }}
<small>
{{'App.AccessKey.AdminTips' | translate }}
</small>
</h5>
<hr>
<section class="form-horizontal">
<div class="alert alert-info no-radius" role="alert">
<strong>Tips:</strong>
<ul>
<li>{{'AccessKey.Tips.1' | translate }}</li>
<li>{{'AccessKey.Tips.2' | translate }}</li>
<li>{{'AccessKey.Tips.3' | translate }}</li>
<ul>
<li>{{'AccessKey.Tips.3.1' | translate }}</li>
<li>{{'AccessKey.Tips.3.2' | translate }}</li>
<li>{{'AccessKey.Tips.3.3' | translate }}</li>
</ul>
</ul>
</div>
</section>
<section>
<div class="row">
<label class="col-sm-1 control-label" style="width: 150px;">
{{'AccessKey.AddButton.Label' | translate }}
</label>
<form class="col-sm-8 form-inline" ng-submit="create()">
<div class="form-group" style="padding-left: 15px">
<div class="form-group">
<select class="form-control input-sm" style="width: 450px;" ng-model="addAccessKeySelectedEnv">
<option value="">{{'Cluster.PleaseChooseEnvironment' | translate }}</option>
<option ng-repeat="env in envs" ng-value="env">{{env}}</option>
</select>
</div>
<button type="submit" class="btn btn-default" style="margin-left: 20px;"
ng-disabled="addAccessKeySelectedEnv == ''">{{'App.Setting.Add' | translate }}
ng-disabled="addAccessKeySelectedEnv == ''">{{'App.Setting.Add' | translate }}
</button>
</form>
</div>
......@@ -72,42 +76,36 @@
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th>{{'AccessKey.ConfigAccessKeys.Secret' | translate }}</th>
<th>{{'AccessKey.ConfigAccessKeys.Status' | translate }}</th>
<th>{{'AccessKey.ConfigAccessKeys.LastModify' | translate }}</th>
<th>{{'AccessKey.ConfigAccessKeys.LastModifyTime' | translate }}</th>
<th>{{'AccessKey.ConfigAccessKeys.Operator' | translate }}</th>
</tr>
<tr>
<th>{{'AccessKey.ConfigAccessKeys.Secret' | translate }}</th>
<th>{{'AccessKey.ConfigAccessKeys.Status' | translate }}</th>
<th>{{'AccessKey.ConfigAccessKeys.LastModify' | translate }}</th>
<th>{{'AccessKey.ConfigAccessKeys.LastModifyTime' | translate }}</th>
<th>{{'AccessKey.ConfigAccessKeys.Operator' | translate }}</th>
</tr>
</thead>
<tbody>
<tr ng-show="(!accessKeys[env] || accessKeys[env].length < 1)">
<td colspan="4">{{'AccessKey.NoAccessKeyServiceTips' | translate }}</td>
</tr>
<tr ng-show="accessKeys[env] && accessKeys[env].length > 0"
ng-repeat="accessKey in accessKeys[env]">
<td style="text-align: center;">{{accessKey.secret}}</td>
<td style="text-align: center;" ng-style="{'color': accessKey.enabled ? '#5cb85c' : '#d20707'}">{{accessKey.enabled ? ('AccessKey.Operator.Enabled' | translate) : ('AccessKey.Operator.Disabled' | translate) }}</td>
<td style="text-align: center;">
{{accessKey.dataChangeLastModifiedBy}}
</td>
<td style="text-align: center;">{{accessKey.dataChangeLastModifiedTime
| date: 'yyyy-MM-dd HH:mm:ss'}}
</td>
<td style="text-align: center;">
<a href="javascript:;"
ng-click="enable(accessKey.id, env)" ng-if="!accessKey.enabled">{{'AccessKey.Operator.Enable' | translate}}</a>
<a href="javascript:;"
ng-click="disable(accessKey.id, env)" ng-if="accessKey.enabled">{{'AccessKey.Operator.Disable' | translate}}</a>
<a href="javascript:;"
ng-click="remove(accessKey.id, env)">{{'AccessKey.Operator.Remove' | translate }}</a>
</td>
</tr>
<tr ng-show="(!accessKeys[env] || accessKeys[env].length < 1)">
<td colspan="5" style="text-align: center;">{{'AccessKey.NoAccessKeyServiceTips' | translate }}</td>
</tr>
<tr ng-show="accessKeys[env] && accessKeys[env].length > 0"
ng-repeat="accessKey in accessKeys[env]">
<td style="text-align: center;">{{accessKey.secret}}</td>
<td style="text-align: center;" ng-style="{'color': accessKey.enabled ? '#5cb85c' : '#d20707'}">{{accessKey.enabled ? ('AccessKey.Operator.Enabled' | translate) : ('AccessKey.Operator.Disabled' | translate) }}</td>
<td style="text-align: center;">{{accessKey.dataChangeLastModifiedBy}}</td>
<td style="text-align: center;">{{accessKey.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'}}</td>
<td style="text-align: center;">
<a href="javascript:;"
ng-click="enable(accessKey.id, env)" ng-if="!accessKey.enabled">{{'AccessKey.Operator.Enable' | translate}}</a>
<a href="javascript:;"
ng-click="disable(accessKey.id, env)" ng-if="accessKey.enabled">{{'AccessKey.Operator.Disable' | translate}}</a>
<a href="javascript:;"
ng-click="remove(accessKey.id, env)" ng-if="!accessKey.enabled">{{'AccessKey.Operator.Remove' | translate }}</a>
</td>
</tr>
</tbody>
</table>
</section>
</section>
<section class="context" ng-show="!hasAssignUserPermission">
......
......@@ -121,9 +121,8 @@
apollo-href="'app/setting.html?#/appid=' + pageContext.appId"></apolloentrance>
<apolloentrance apollo-title="'Config.AccessKeyManage' | translate"
apollo-img-src="'project-manage'"
apollo-href="'/app/access_key.html?#/appid=' + pageContext.appId"
ng-show="hasCreateClusterPermission"></apolloentrance>
apollo-img-src="'accesskey-manage'"
apollo-href="'/app/access_key.html?#/appid=' + pageContext.appId"></apolloentrance>
<a class="list-group-item" ng-show="missEnvs.length > 0" ng-click="createAppInMissEnv()">
<div class="row icon-text icon-plus-orange">
......
......@@ -464,9 +464,14 @@
"ServiceConfig.PleaseEnterKey": "Please enter key",
"ServiceConfig.KeyNotExistsAndCreateTip": "Key: '{{key}}' does not exist. Click Save to create the configuration item.",
"ServiceConfig.KeyExistsAndSaveTip": "Key: '{{key}}' already exists. Click Save will override the configuration item.",
"AccessKey.AddButton.Label": "Create AccessKey",
"AccessKey.NoAccessKeyServiceTips": "No accessKey found!",
"AccessKey.ConfigAccessKeys.Secret": "AccessKey Secret",
"AccessKey.Tips.1": "Add up to 5 access keys per environment.",
"AccessKey.Tips.2": "Once the environment has any enabled access key, the client will be required to configure access key, or the configurations cannot be obtained.",
"AccessKey.Tips.3": "Configure the access key to prevent unauthorized clients from obtaining the application configuration. The configuration method is as follows:",
"AccessKey.Tips.3.1": "Via jvm parameter -Dapollo.accesskey.secret",
"AccessKey.Tips.3.2": "Through the environment variable APOLLO_ACCESSKEY_SECRET",
"AccessKey.Tips.3.3": "Configure apollo.accesskey.secret via META-INF/app.properties or application.properties (note that the multi-environment secret is different)",
"AccessKey.NoAccessKeyServiceTips": "There are no access keys in this environment.",
"AccessKey.ConfigAccessKeys.Secret": "Access Key Secret",
"AccessKey.ConfigAccessKeys.Status": "Status",
"AccessKey.ConfigAccessKeys.LastModify": "Last Modifier",
"AccessKey.ConfigAccessKeys.LastModifyTime": "Last Modified Time",
......@@ -476,18 +481,18 @@
"AccessKey.Operator.Disabled": "Disabled",
"AccessKey.Operator.Enabled": "Enabled",
"AccessKey.Operator.Remove": "Remove",
"AccessKey.Operator.CreateSuccess": "Create accessKey for '{{env}}' success",
"AccessKey.Operator.DisabledSuccess": "Disabled accessKey for '{{env}}' success",
"AccessKey.Operator.EnabledSuccess": "Enabled accessKey for '{{env}}' success",
"AccessKey.Operator.RemoveSuccess": "Remove accessKey for '{{env}}' success",
"AccessKey.Operator.CreateError": "Failed to create accessKey for '{{env}}'",
"AccessKey.Operator.DisabledError": "Failed to disabled accessKey for '{{env}}'",
"AccessKey.Operator.EnabledError": "Failed to enabled accessKey for '{{env}}'",
"AccessKey.Operator.RemoveError": "Failed to remove accessKey for '{{env}}'",
"AccessKey.Operator.DisabledTips": "Are you sure disable {{env}} AccessKey?",
"AccessKey.Operator.EnabledTips": "Are you sure enable {{env}} AccessKey?",
"AccessKey.Operator.RemoveTips": "Are you sure remove {{env}} AccessKey?",
"AccessKey.LoadError": "Failed to load accessKeys for '{{env}}'",
"AccessKey.Operator.CreateSuccess": "Access key created successfully",
"AccessKey.Operator.DisabledSuccess": "Access key disabled successfully",
"AccessKey.Operator.EnabledSuccess": "Access key enabled successfully",
"AccessKey.Operator.RemoveSuccess": "Access key removed successfully",
"AccessKey.Operator.CreateError": "Access key created failed",
"AccessKey.Operator.DisabledError": "Access key disabled failed",
"AccessKey.Operator.EnabledError": "Access key enabled failed",
"AccessKey.Operator.RemoveError": "Access key removed failed",
"AccessKey.Operator.DisabledTips": "Are you sure you want to disable the access key?",
"AccessKey.Operator.EnabledTips": "Are you sure you want to enable the access key?",
"AccessKey.Operator.RemoveTips": "Are you sure you want to remove the access key?",
"AccessKey.LoadError": "Error Loading access keys",
"SystemInfo.Title": "System Information",
"SystemInfo.SystemVersion": "System version",
"SystemInfo.Tips1": "The environment list comes from the <strong>apollo.portal.envs</strong> configuration in Apollo PortalDB.ServerConfig, and can be configured in <a href=\"{{serverConfigUrl}}\">System Configuration</a> page. For more information, please refer the <strong>apollo.portal.envs - supportable environment list</strong> section in <a href=\"{{wikiUrl}}\">Distributed Deployment Guide</a>.",
......@@ -674,7 +679,6 @@
"App.AppOwnerTips": "(After enabling the application administrator allocation restrictions, the application owner and project administrator are default to current account, not subject to change)",
"App.AppAdminTips1": "(The application owner has project administrator permission by default.",
"App.AppAdminTips2": "Project administrators can create namespace, cluster, and assign user permissions)",
"App.AccessKey.AdminTips": "(Project administrators can config accessKey)",
"App.AccessKey.NoPermissionTips": "You do not have permission to operate, please ask [{{users}}] to authorize",
"App.Setting.Title": "Manage Project",
"App.Setting.Admin": "Administrators",
......
......@@ -464,9 +464,14 @@
"ServiceConfig.PleaseEnterKey": "请输入key",
"ServiceConfig.KeyNotExistsAndCreateTip": "Key: '{{key}}' 不存在,点击保存后会创建该配置项",
"ServiceConfig.KeyExistsAndSaveTip": "Key: '{{key}}' 已存在,点击保存后会覆盖该配置项",
"AccessKey.AddButton.Label": "创建AccessKey",
"AccessKey.NoAccessKeyServiceTips": "No accessKey found!",
"AccessKey.ConfigAccessKeys.Secret": "AccessKey Secret",
"AccessKey.Tips.1": "每个环境最多可添加5个访问密钥",
"AccessKey.Tips.2": "一旦该环境有启用的访问密钥,客户端将被要求配置密钥,否则无法获取配置",
"AccessKey.Tips.3": "配置访问密钥防止非法客户端获取护应用配置,配置方式如下:",
"AccessKey.Tips.3.1": "通过jvm参数-Dapollo.accesskey.secret",
"AccessKey.Tips.3.2": "通过环境变量APOLLO_ACCESSKEY_SECRET",
"AccessKey.Tips.3.3": "通过META-INF/app.properties或application.properties配置apollo.accesskey.secret(注意多环境secret不一样)",
"AccessKey.NoAccessKeyServiceTips": "该环境没有配置访问密钥",
"AccessKey.ConfigAccessKeys.Secret": "访问密钥",
"AccessKey.ConfigAccessKeys.Status": "状态",
"AccessKey.ConfigAccessKeys.LastModify": "最后修改人",
"AccessKey.ConfigAccessKeys.LastModifyTime": "最后修改时间",
......@@ -476,18 +481,18 @@
"AccessKey.Operator.Disabled": "已禁用",
"AccessKey.Operator.Enabled": "已启用",
"AccessKey.Operator.Remove": "删除",
"AccessKey.Operator.CreateSuccess": " {{env}}环境AccessKey创建成功",
"AccessKey.Operator.DisabledSuccess": " {{env}}环境AccessKey禁用成功",
"AccessKey.Operator.EnabledSuccess": " {{env}}环境AccessKey启用成功",
"AccessKey.Operator.RemoveSuccess": " {{env}}环境AccessKey删除成功",
"AccessKey.Operator.CreateError": " {{env}}环境AccessKey创建失败",
"AccessKey.Operator.DisabledError": " {{env}}环境AccessKey禁用失败",
"AccessKey.Operator.EnabledError": " {{env}}环境AccessKey启用失败",
"AccessKey.Operator.RemoveError": " {{env}}环境AccessKey删除失败",
"AccessKey.Operator.DisabledTips": "是否确定禁用{{env}}AccessKey?",
"AccessKey.Operator.EnabledTips": " 是否确定启用{{env}}AccessKey?",
"AccessKey.Operator.RemoveTips": " 是否确定删除{{env}}AccessKey?",
"AccessKey.LoadError": "加载 '{{env}}' AccessKeys出错",
"AccessKey.Operator.CreateSuccess": "访问密钥创建成功",
"AccessKey.Operator.DisabledSuccess": "访问密钥禁用成功",
"AccessKey.Operator.EnabledSuccess": "访问密钥启用成功",
"AccessKey.Operator.RemoveSuccess": "访问密钥删除成功",
"AccessKey.Operator.CreateError": "访问密钥创建失败",
"AccessKey.Operator.DisabledError": "访问密钥禁用失败",
"AccessKey.Operator.EnabledError": "访问密钥启用失败",
"AccessKey.Operator.RemoveError": "访问密钥删除失败",
"AccessKey.Operator.DisabledTips": "是否确定禁用该访问密钥?",
"AccessKey.Operator.EnabledTips": " 是否确定启用该访问密钥?",
"AccessKey.Operator.RemoveTips": " 是否确定删除该访问密钥?",
"AccessKey.LoadError": "加载访问密钥出错",
"SystemInfo.Title": "系统信息",
"SystemInfo.SystemVersion": "系统版本",
"SystemInfo.Tips1": "环境列表来自于ApolloPortalDB.ServerConfig中的<strong>apollo.portal.envs</strong>配置,可以到<a href=\"{{serverConfigUrl}}\">系统参数</a>页面配置,更多信息可以参考<a href=\"{{wikiUrl}}\">分布式部署指南</a>中的<strong>apollo.portal.envs - 可支持的环境列表</strong>章节。",
......@@ -674,7 +679,6 @@
"App.AppOwnerTips": "(开启项目管理员分配权限控制后,应用负责人和项目管理员默认为本账号,不可选择)",
"App.AppAdminTips1": "(应用负责人默认具有项目管理员权限,",
"App.AppAdminTips2": "项目管理员可以创建Namespace和集群、分配用户权限)",
"App.AccessKey.AdminTips": "(项目管理员可以配置秘钥)",
"App.AccessKey.NoPermissionTips": "您没有权限操作,请找 [{{users}}] 开通权限",
"App.Setting.Title": "项目管理",
"App.Setting.Admin": "管理员",
......
......@@ -33,49 +33,30 @@ function AccessKeyController($scope, $location, $translate, toastr,
init();
function init() {
initEnv();
initPermission();
initAdmins();
initApplication();
}
function initEnv() {
EnvService.find_all_envs()
.then(function (result) {
$scope.envs = result;
initAccessKeys();
});
}
function initPermission() {
PermissionService.has_assign_user_permission($scope.pageContext.appId)
.then(function (result) {
$scope.hasAssignUserPermission = result.hasPermission;
PermissionService.has_open_manage_app_master_role_limit().then(function (value) {
if (!value.isManageAppMasterPermissionEnabled) {
$scope.hasManageAppMasterPermission = $scope.hasAssignUserPermission;
return;
}
PermissionService.has_manage_app_master_permission($scope.pageContext.appId).then(function (res) {
$scope.hasManageAppMasterPermission = res.hasPermission && $scope.hasAssignUserPermission;
PermissionService.has_root_permission().then(function (value) {
$scope.hasManageAppMasterPermission = value.hasPermission || $scope.hasManageAppMasterPermission;
});
});
});
if (result.hasPermission) {
initEnv();
}
});
}
function initApplication() {
AppService.load($scope.pageContext.appId).then(function (app) {
$scope.app = app;
$scope.viewApp = _.clone(app);
$('.project-setting .panel').removeClass('hidden');
})
function initEnv() {
EnvService.find_all_envs()
.then(function (result) {
$scope.envs = result;
initAccessKeys();
});
}
function initAccessKeys() {
$scope.accessKeys = {};
for (var iLoop = 0; iLoop < $scope.envs.length; iLoop++) {
......@@ -92,6 +73,25 @@ function AccessKeyController($scope, $location, $translate, toastr,
});
}
function initAdmins() {
PermissionService.get_app_role_users($scope.pageContext.appId)
.then(function (result) {
$scope.appRoleUsers = result;
$scope.admins = [];
$scope.appRoleUsers.masterUsers.forEach(function (user) {
$scope.admins.push(user.userId);
});
});
}
function initApplication() {
AppService.load($scope.pageContext.appId).then(function (app) {
$scope.app = app;
$scope.viewApp = _.clone(app);
$('.project-setting .panel').removeClass('hidden');
})
}
function create() {
var env = $scope.addAccessKeySelectedEnv;
UserService.load_user().then(function (result) {
......
......@@ -345,19 +345,13 @@ function ConfigBaseInfoController($rootScope, $scope, $window, $location, $trans
});
PermissionService.has_assign_user_permission(appId).then(function (result) {
$scope.hasAssignUserPermission = result.hasPermission;
}, function (result) {
});
PermissionService.has_manage_access_key_permission(appId).then(
function (result) {
$scope.hasCreateClusterPermission = result.hasPermission;
}, function (result) {
});
$scope.showMasterPermissionTips = function () {
$("#masterNoPermissionDialog").modal('show');
};
......
......@@ -212,9 +212,6 @@ appService.service('PermissionService', ['$resource', '$q', 'AppUtil', function
has_assign_user_permission: function (appId) {
return hasAppPermission(appId, 'AssignRole');
},
has_manage_access_key_permission: function (appId) {
return hasAppPermission(appId, 'ManageAccessKey');
},
has_modify_namespace_permission: function (appId, namespaceName) {
return hasNamespacePermission(appId, namespaceName, 'ModifyNamespace');
},
......
......@@ -505,6 +505,10 @@ table th {
background: url(../img/manage.png) no-repeat;
}
.list-group-item .icon-accesskey-manage {
background: url(../img/secret.png) no-repeat;
}
.list-group-item .icon-plus-orange {
background: url(../img/add.png) no-repeat;
}
......@@ -803,6 +807,7 @@ table th {
.project-setting .panel-body .context {
padding-left: 30px;
padding-right: 30px;
}
.app-search-list .select2-container, .app-search-list .select2-container .select2-selection {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment