Commit 93374307 authored by Rucha Deshpande's avatar Rucha Deshpande
Browse files

AWS API implementation, integration tests and bootstrap script

commit 8db9e8fd 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Wed Apr 21 2021 11:23:58 GMT-0500 (Central Daylight Time) 

    Added a comment


commit a163cc86 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Wed Apr 21 2021 11:20:17 GMT-0500 (Central Daylight Time) 

    Added a comment


commit 31c23b5e 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Wed Apr 21 2021 09:00:18 GMT-0500 (Central Daylight Time) 

    remove comment from script


commit 89fdd8d3 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Wed Apr 21 2021 08:58:01 GMT-0500 (Central Daylight Time) 

    Add copyright


commit c88ea34e 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Tue Apr 20 2021 17:06:20 GMT-0500 (Central Daylight Time) 

    add getProtectedMembers


commit e227c574 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Tue Apr 20 2021 16:55:33 GMT-0500 (Central Daylight Time) 

    update springfox version


commit 37a59a59 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Tue Apr 20 2021 10:02:16 GMT-0500 (Central Daylight Time) 

    remove comments


commit cd09da0c 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Tue Apr 20 2021 09:56:47 GMT-0500 (Central Daylight Time) 

    Add copyright stmt


commit 67ac467d 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Tue Apr 20 2021 09:31:19 GMT-0500 (Central Daylight Time) 

    remove unwanted properties


commit f619fd4d 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Mon Apr 19 2021 11:10:20 GMT-0500 (Central Daylight Time) 

    Add bootstrap scripts


commit c7ba3273 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Fri Apr 16 2021 13:41:20 GMT-0500 (Central Daylight Time) 

    Add chmod to sh files


commit 64c85c51 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Fri Apr 16 2021 13:33:53 GMT-0500 (Central Daylight Time) 

    delete unit test for now


commit 62fa1a54 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Fri Apr 16 2021 13:27:52 GMT-0500 (Central Daylight Time) 

    Add prepare-dist to build


commit 31205a13 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Fri Apr 16 2021 12:25:03 GMT-0500 (Central Daylight Time) 

    Add AWS impl


commit 5185fa58 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Mon Apr 12 2021 11:26:40 GMT-0500 (Central Daylight Time) 

    Add authorizer to interceptor


commit 52f5804a 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Mon Apr 12 2021 10:27:50 GMT-0500 (Central Daylight Time) 

    add qq to buildspec


commit 77477c68 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Mon Apr 12 2021 09:53:32 GMT-0500 (Central Daylight Time) 

    Add interceptor


commit c64ff655 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Wed Mar 31 2021 10:17:53 GMT-0500 (Central Daylight Time) 

    merge with dev


commit fd4979f7 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Wed Mar 31 2021 10:16:14 GMT-0500 (Central Daylight Time) 

    delete unwanted files


commit 6d0da95b 
Author: Rucha Deshpande <deshruch@amazon.com> 
Date: Wed Mar 31 2021 09:51:05 GMT-0500 (Central Daylight Time) 

    aws specific changes
parent 0dc9a6fc
......@@ -6,3 +6,4 @@
**/*.iml
**/dependency-reduced-pom.xml
**/*.pyc
/dist/
......@@ -3,6 +3,14 @@ variables:
AZURE_BUILD_SUBDIR: provider/entitlements-v2-azure
AZURE_TEST_SUBDIR: testing/entitlements-v2-test-azure
AWS_BUILD_SUBDIR: provider/entitlements-v2-aws/build-aws
AWS_TEST_SUBDIR: testing/entitlements-v2-test-aws
AWS_SERVICE: entitlementsV2
AWS_ENVIRONMENT: dev
include:
- project: "osdu/platform/ci-cd-pipelines"
file: "standard-setup.yml"
......@@ -19,5 +27,8 @@ include:
- project: "osdu/platform/ci-cd-pipelines"
file: "cloud-providers/azure.yml"
- project: "osdu/platform/ci-cd-pipelines"
file: "cloud-providers/aws.yml"
- project: "osdu/platform/ci-cd-pipelines"
file: "publishing/pages.yml"
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os;
import boto3;
import jwt;
import requests;
import base64;
import json;
class AwsToken(object):
def get_aws_id_token(self):
if os.getenv("AWS_COGNITO_REGION") is not None:
region = os.environ["AWS_COGNITO_REGION"]
else:
region = os.environ["AWS_REGION"]
client = boto3.client('cognito-idp', region_name=region)
environment = os.environ["RESOURCE_PREFIX"]
ssm = boto3.client('ssm')
secretsmanagerclient = boto3.client('secretsmanager')
tokenUrl = ssm.get_parameter(Name='/osdu/' + environment +'/oauth-token-uri', WithDecryption=True)['Parameter']['Value']
awsOauthCustomScope = ssm.get_parameter(Name='/osdu/' + environment +'/oauth-custom-scope', WithDecryption=True)['Parameter']['Value']
client_credentials_clientid = ssm.get_parameter(Name='/osdu/' + environment +'/client-credentials-client-id', WithDecryption=True)['Parameter']['Value']
client_secret_key = 'client_credentials_client_secret'
client_secret_secretName = '/osdu/' + environment +'/client_credentials_secret'
client_credentials_secret=''
get_secret_value_response = secretsmanagerclient.get_secret_value(SecretId=client_secret_secretName)
if 'SecretString' in get_secret_value_response:
secret = json.loads(get_secret_value_response['SecretString'])
client_credentials_secret=secret['client_credentials_client_secret']
encodeThisString = client_credentials_clientid+':'+client_credentials_secret
encodeThisStringBytes=encodeThisString.encode('UTF-8')
authorizationHeaderContents=base64.b64encode(encodeThisStringBytes)
a= authorizationHeaderContents.decode("UTF-8")
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic '+a
}
method = 'POST'
url = tokenUrl+'?grant_type=client_credentials&client_id='+client_credentials_clientid+'&scope='+awsOauthCustomScope;
response = requests.request(method,url, headers=headers)
jsonResponse = response.json()
for key, value in jsonResponse.items():
if(key == 'access_token'):
token = 'Bearer ' + value
print(token)
return token
if __name__ == '__main__':
AwsToken().get_aws_id_token()
\ No newline at end of file
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#Required Env Variables
#AWS_BASE_URL
#AWS_DEPLOYMENTS_SUBDIR
#AWS_REGION
pip3 install -r $AWS_DEPLOYMENTS_SUBDIR/requirements.txt
echo $AWS_BASE_URL
export AWS_ENTITLEMENTSV2_SERVICE_URL=$AWS_BASE_URL/api/entitlements/v2/
if [ -z "$BEARER_TOKEN" ];
then BEARER_TOKEN=`python $AWS_DEPLOYMENTS_SUBDIR/Token.py`;
export BEARER_TOKEN=$BEARER_TOKEN
fi
export APP_KEY=""
export DATA_PARTITION=opendes
python $AWS_DEPLOYMENTS_SUBDIR/initTenant.py -u $AWS_ENTITLEMENTSV2_SERVICE_URL -t $DATA_PARTITION
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import os
import requests
parser = argparse.ArgumentParser()
parser.add_argument('-u', help='The complete URL to the Entitlements V2 Service.', default=None)
parser.add_argument('-t', help='The tenant for which groups are being provisioned.', default=None)
arguments = parser.parse_args()
if arguments.u is not None:
url = arguments.u
if arguments.t is not None:
tenant = arguments.t
token = os.environ.get('BEARER_TOKEN')
initTenantUrl=url+'tenant-provisioning'
#call init API to provision groups for Service Principal
headers = {
'data-partition-id': tenant,
'Content-Type': 'application/json',
'Authorization': token
}
method = 'POST'
response = requests.request(method,initTenantUrl, headers=headers)
print(response.status_code)
if response.status_code==200:
print('The Entitlements V2 bootstrapping successful for tenant:'+tenant)
else:
print('The Entitlements V2 bootstrapping failed for tenant:'+tenant)
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# THIS SCRIPT MUST BE RUN FROM THE ROOT FOLDER OF THE ENTITLEMENTS V2 SERVICE
set -e
OUTPUT_DIR="${OUTPUT_DIR:-dist}"
echo "--Copying Entitlements V2 Boostrap Scripts to ${OUTPUT_DIR}--"
rm -rf "${OUTPUT_DIR}/devops"
mkdir -p "${OUTPUT_DIR}/devops/aws"
cp -r devops/aws/ "${OUTPUT_DIR}/devops/"
boto3==1.17.1
botocore==1.20.1
PyJWT==1.7.1
requests
argparse
\ No newline at end of file
# Copyright © 2021 Amazon Web Services
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
......@@ -11,7 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html
FROM amazoncorretto:8
......
# Copyright © 2021 Amazon Web Services
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
......
# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
......@@ -30,7 +30,7 @@ phases:
# fix error noted here: https://github.com/yarnpkg/yarn/issues/7866
- curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
- if [ $(echo $CODEBUILD_SOURCE_VERSION | grep -c ^refs/heads.*) -eq 1 ]; then echo "Branch name found"; else echo "This build only supports branch builds" && exit 1; fi
- apt-get update -y
- apt-get update -y -qq
- apt-get install -y maven
- java -version
- mvn -version
......@@ -60,8 +60,12 @@ phases:
- echo "Building primary service assemblies..."
- mvn -ntp -B test install -pl entitlements-v2-core,provider/entitlements-v2-aws -Ddeployment.environment=prod
#- echo "Building integration testing assemblies and gathering artifacts..."
#- ./testing/entitlements-v2-test-aws/build-aws/prepare-dist.sh
- echo "Building integration testing assemblies and gathering artifacts..."
- ./testing/entitlements-v2-test-aws/build-aws/prepare-dist.sh
#Copy ENTITLEMENTS V2 bootstrap scripts to dist
- ./devops/aws/prepare-dist.sh
- echo "Logging into Docker Hub..."
- docker login -u ${DOCKER_USERNAME} -p ${DOCKER_PASSWORD}
......
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
if [ -n $USE_SELF_SIGNED_SSL_CERT ];
then
......
# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
......
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
......
......@@ -6,7 +6,7 @@
<parent>
<artifactId>entitlements-v2-service</artifactId>
<groupId>org.opengroup.osdu.entitlements.v2</groupId>
<version>0.8.0-SNAPSHOT</version>
<version>0.9.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
......@@ -16,8 +16,9 @@
<core-lib-aws.version>0.3.16</core-lib-aws.version>
<reactor.netty.version>0.9.5.RELEASE</reactor.netty.version>
<reactor.core.version>3.3.0.RELEASE</reactor.core.version>
<springfox-version>2.7.0</springfox-version>
<springfox-version>3.0.0</springfox-version>
<tomcat-embed-core.version>9.0.37</tomcat-embed-core.version>
<org.springframework.boot.version>2.4.4</org.springframework.boot.version>
</properties>
<dependencies>
......@@ -54,10 +55,32 @@
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat-embed-core.version}</version>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.7.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.0</version>
<!--<version>2.4.0</version>-->
<version>${spring-boot-dependencies.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
......
// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
......@@ -18,7 +18,6 @@ package org.opengroup.osdu.entitlements.v2.aws;
import lombok.Getter;
import org.opengroup.osdu.entitlements.v2.AppProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
......@@ -29,6 +28,53 @@ import java.util.List;
@Getter
public class AwsAppProperties extends AppProperties {
public static final String DEFAULT_APPID_KEY = "NA";
@Value("${redishost}")
private String redishost;
@Value("${redisport}")
private String redisport;
@Value("${redis.partition.association}")
private int redispartitionAssociation;
@Value("${partition.entitynode}")
private int partitionEntityNode;
@Value("${partition.parent.ref}")
private int partitionParentRef;
@Value("${partition.children.ref}")
private int partitionChildrenRef;
@Value("${partition.appid}")
private int partitionAppId;
public String getRedisHost() {
return redishost;
}
public int getRedisPort() {
return Integer.parseInt(redisport);
}
public int getRedisPartitionAssociation() {
return redispartitionAssociation;
}
public int getPartitionEntityNode() {
return partitionEntityNode;
}
public int getPartitionParentRef() {
return partitionParentRef;
}
public int getPartitionChildrenRef() {
return partitionChildrenRef;
}
public int getPartitionAppId() {
return partitionAppId;
}
@Override
public List<String> getInitialGroups() {
......@@ -43,4 +89,12 @@ public class AwsAppProperties extends AppProperties {
public String getGroupsOfServicePrincipal() {
return "/provisioning/accounts/groups_of_service_principal.json";
}
@Override
public List<String> getProtectedMembers() {
List<String> filePaths = new ArrayList<>();
filePaths.add("/provisioning/groups/data_groups.json");
filePaths.add("/provisioning/groups/datalake_service_groups.json");
return filePaths;
}
}
// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
......
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.opengroup.osdu.entitlements.v2.aws.interceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private RequestHeaderInterceptor requestHeaderInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestHeaderInterceptor).addPathPatterns("/**").excludePathPatterns("/_ah/**");
}
}
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.opengroup.osdu.entitlements.v2.aws.interceptor;
import org.opengroup.osdu.core.aws.entitlements.Authorizer;
import org.opengroup.osdu.core.aws.entitlements.RequestKeys;
import org.opengroup.osdu.core.aws.lambda.HttpStatusCodes;
import org.opengroup.osdu.core.common.logging.JaxRsDpsLog;
import org.opengroup.osdu.core.common.model.http.AppException;
import org.opengroup.osdu.core.common.model.http.DpsHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
@Component
public class RequestHeaderInterceptor implements HandlerInterceptor {
@Value("${aws.region}")
private String awsRegion;
@Value("${aws.environment}")
private String awsEnvironment;
@Autowired
DpsHeaders dpsheaders;
@Autowired
private JaxRsDpsLog log;
Authorizer authorizer;
@PostConstruct
public void init() {
authorizer = new Authorizer(awsRegion, awsEnvironment);
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
log.info("Intercepted the request. Now validating token..");
Map<String,String> headers = dpsheaders.getHeaders();
//validate JWT token here
// extract custom attribute from the JWT token using OAuth 2.0 and
String memberEmail = validateJwt(headers);
if(memberEmail!=null) {
// If JWT validation succeeds/ can extract user identity
// return true = requests moves forward to the core API code
// Update the request Header to include user identity
headers.put("x-user-id", memberEmail);
return true;
}
else //If JWT validation fails/ cannot extract user identity
// return false = response is handled here
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
public String validateJwt(Map<String, String> headers)
{
int httpStatusCode = HttpStatusCodes.UNASSIGNED;
String memberEmail=null;
// check for valid JWT
// authorization header is lowercase in osdu services but standard is uppercase first letter
String authorizationContents = headers.get(RequestKeys.AUTHORIZATION_HEADER_KEY);
if(authorizationContents == null){
authorizationContents = headers.get(RequestKeys.AUTHORIZATION_HEADER_KEY.toLowerCase());
}
//no JWT
if(authorizationContents == null)
{
throw AppException.createForbidden("No JWT token. Access is Forbidden");
}
memberEmail = authorizer.validateJWT(authorizationContents);
return memberEmail;
}
}
// Copyright © 2020 Amazon Web Services
// Copyright © Microsoft Corporation
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
...