Compute
Elastic Cloud Server
Huawei Cloud Flexus
Bare Metal Server
Auto Scaling
Image Management Service
Dedicated Host
FunctionGraph
Cloud Phone Host
Huawei Cloud EulerOS
Networking
Virtual Private Cloud
Elastic IP
Elastic Load Balance
NAT Gateway
Direct Connect
Virtual Private Network
VPC Endpoint
Cloud Connect
Enterprise Router
Enterprise Switch
Global Accelerator
Management & Governance
Cloud Eye
Identity and Access Management
Cloud Trace Service
Resource Formation Service
Tag Management Service
Log Tank Service
Config
OneAccess
Resource Access Manager
Simple Message Notification
Application Performance Management
Application Operations Management
Organizations
Optimization Advisor
IAM Identity Center
Cloud Operations Center
Resource Governance Center
Migration
Server Migration Service
Object Storage Migration Service
Cloud Data Migration
Migration Center
Cloud Ecosystem
KooGallery
Partner Center
User Support
My Account
Billing Center
Cost Center
Resource Center
Enterprise Management
Service Tickets
HUAWEI CLOUD (International) FAQs
ICP Filing
Support Plans
My Credentials
Customer Operation Capabilities
Partner Support Plans
Professional Services
Analytics
MapReduce Service
Data Lake Insight
CloudTable Service
Cloud Search Service
Data Lake Visualization
Data Ingestion Service
GaussDB(DWS)
DataArts Studio
Data Lake Factory
DataArts Lake Formation
IoT
IoT Device Access
Others
Product Pricing Details
System Permissions
Console Quick Start
Common FAQs
Instructions for Associating with a HUAWEI CLOUD Partner
Message Center
Security & Compliance
Security Technologies and Applications
Web Application Firewall
Host Security Service
Cloud Firewall
SecMaster
Anti-DDoS Service
Data Encryption Workshop
Database Security Service
Cloud Bastion Host
Data Security Center
Cloud Certificate Manager
Edge Security
Situation Awareness
Managed Threat Detection
Blockchain
Blockchain Service
Web3 Node Engine Service
Media Services
Media Processing Center
Video On Demand
Live
SparkRTC
MetaStudio
Storage
Object Storage Service
Elastic Volume Service
Cloud Backup and Recovery
Storage Disaster Recovery Service
Scalable File Service Turbo
Scalable File Service
Volume Backup Service
Cloud Server Backup Service
Data Express Service
Dedicated Distributed Storage Service
Containers
Cloud Container Engine
SoftWare Repository for Container
Application Service Mesh
Ubiquitous Cloud Native Service
Cloud Container Instance
Databases
Relational Database Service
Document Database Service
Data Admin Service
Data Replication Service
GeminiDB
GaussDB
Distributed Database Middleware
Database and Application Migration UGO
TaurusDB
Middleware
Distributed Cache Service
API Gateway
Distributed Message Service for Kafka
Distributed Message Service for RabbitMQ
Distributed Message Service for RocketMQ
Cloud Service Engine
Multi-Site High Availability Service
EventGrid
Dedicated Cloud
Dedicated Computing Cluster
Business Applications
Workspace
ROMA Connect
Message & SMS
Domain Name Service
Edge Data Center Management
Meeting
AI
Face Recognition Service
Graph Engine Service
Content Moderation
Image Recognition
Optical Character Recognition
ModelArts
ImageSearch
Conversational Bot Service
Speech Interaction Service
Huawei HiLens
Video Intelligent Analysis Service
Developer Tools
SDK Developer Guide
API Request Signing Guide
Terraform
Koo Command Line Interface
Content Delivery & Edge Computing
Content Delivery Network
Intelligent EdgeFabric
CloudPond
Intelligent EdgeCloud
Solutions
SAP Cloud
High Performance Computing
Developer Services
ServiceStage
CodeArts
CodeArts PerfTest
CodeArts Req
CodeArts Pipeline
CodeArts Build
CodeArts Deploy
CodeArts Artifact
CodeArts TestPlan
CodeArts Check
CodeArts Repo
Cloud Application Engine
MacroVerse aPaaS
KooMessage
KooPhone
KooDrive

Using an Authorization Header

Updated on 2024-09-29 GMT+08:00

Function

Using a header for authentication is the most common method of carrying a signature, supported by all OBS APIs. With this method, the signature is carried in the Authorization header of a request, as shown in Figure 1.

Figure 1 An Authorization header

The header format is:

Authorization: OBS AccessKeyID:Signature

An OBS string, an access key ID (AK), and a signature comprise an Authorization header. The string OBS and AK are separated by a space, and the AK and signature are separated by a colon (:).

  • To obtain an access key ID (AK), see Access Keys.
  • To calculate a signature, see Table 1.
    Table 1 Calculating a signature carried in the Authorization header

    Method

    Description

    Link

    Using SDKs

    All available OBS SDKs provide automatic calculation. Save time by using them directly.

    Using SDKs

    Using signature generators

    OBS provides graphical tools to make it easier to generate signatures.

    Using a Signature Generator

    Manual calculation

    You can manually calculate a signature using the provided signing algorithm.

    Manually Calculating a Signature

Using SDKs

OBS SDKs provide built-in signature calculation, so you do not need to take care of this operation. Table 2 lists the source code files for signature calculation for OBS SDKs in different languages.

Table 2 Signature source files of OBS SDKs

Using SDKs

Signature Source File

Java

RestStorageService.java

Python

auth.py

Go

auth.go

C

request.c

Node.Js

utils.js

Browser.Js

utils.js

PHP

DefaultSignature.php

.NET

Signer.cs

Using a Signature Generator

OBS provides a graphical tool to make it easier to generate signatures. You can find the tool here. To learn how to use the tool, see Using Signature Generators.

Manually Calculating a Signature

Use the following algorithm to calculate the signature carried in the header:

Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) )

The following figure shows how the signature carried in the header is calculated:

Figure 2 Calculating a signature carried in the header
  1. Construct a StringToSign. Below are the StringToSign structure and parameters. For more StringToSign examples, see Example StringToSign.

    StringToSign = 
        HTTP-Verb + "\n" + 
        Content-MD5 + "\n" + 
        Content-Type + "\n" + 
        Date + "\n" + 
        CanonicalizedHeaders + "\n" +
        CanonicalizedResource
    Table 3 Parameters required for constructing a StringToSign

    Parameter

    Type

    Mandatory (Yes/No)

    Description

    HTTP-Verb

    String

    Yes

    Explanation:

    The HTTP method used to make a request (also called an operation). For RESTful APIs, HTTP methods include PUT, GET, DELETE, and other operations. Select a method based on the API to be called.

    Restrictions:

    None

    Value range:

    • GET: Requests that a server return a specific resource, for example, obtaining a bucket list or downloading an object.
    • PUT: Requests that a server update a specific resource, for example, creating a bucket or uploading an object.
    • POST: Requests that a server add a resource or perform special operations such as initiating a multipart upload or assembling parts.
    • DELETE: Requests that a server delete a specific resource such as an object.
    • HEAD: Requests that a server return the description of a specific resource, for example, obtaining object metadata.
    • OPTIONS (not supported for signature generators): Requests that a server check whether the user has the permissions to perform an operation on a resource. CORS must be configured for the bucket.

    Default value:

    None

    Content-MD5

    String

    No

    Explanation:

    The base64-encoded 128-bit MD5 digest of the request body based on RFC 1864.

    Restrictions:

    None

    Value range:

    A string of 22 characters.

    Default value:

    This parameter is left blank by default.

    Content-Type

    String

    No

    Explanation:

    The file type of an object—for example, text/plain—which determines what format and encoding a browser uses to read the file.

    Restrictions:

    None

    Value range:

    See What Is Content-Type (MIME)?

    Default value:

    If this parameter is not included, an empty string is used by default. Table 4 shows an example.

    Date

    String

    Yes

    Explanation:

    When a request was made. If the value of Date is over 15 minutes away from the current server time, the server returns 403, indicating that the request is invalid.

    Restrictions:

    • The value must be a GMT in RFC 1123 format.
    • If Date and x-obs-date headers are both specified, x-obs-date applies.

    Value range:

    None

    Default value:

    None

    CanonicalizedHeaders

    String

    No

    Explanation:

    Additional headers defined by OBS that include the x-obs- prefix, for example, x-obs-date, x-obs-acl, and x-obs-meta-*. For each additional header, separate its name and value by a colon (:). In x-obs-storage-class:STANDARD, for example, x-obs-storage-class is the header name, and STANDARD is the header value.

    Restrictions:

    1. Header names must be lowercase. The header value is case sensitive. An example header is x-obs-storage-class:STANDARD.
    2. A header name cannot contain non-ASCII or unrecognizable characters, which are also not recommended for header values. If such characters are necessary, they must be encoded or decoded in URL or Base64 on the client side, because the server side does not perform any decoding.
    3. A header cannot contain meaningless tabs or spaces. For example, x-obs-meta-name: name (with a meaningless space before name) must be changed to x-obs-meta-name:name.
    4. If multiple headers are involved, they must be sorted in ascending lexicographic order by header name.
    5. If a header has multiple values, these values need to be written together under their shared header name, separated by commas (,). For example, x-obs-meta-name:name1 and x-obs-meta-name:name2 must be combined into x-obs-meta-name:name1,name2.
    6. Each header requires a new line, and each line ends with a newline character (\n).

    Value range:

    Determined by the API to be called

    Default value:

    This parameter is left blank by default.

    CanonicalizedResource

    String

    Yes

    Explanation:

    OBS resources specified in an HTTP request. The structure is as follows:

    CanonicalizedResource = "/" + bucket-name +  "/" + object-name + "?" + sub-resource 

    For example, if you want to call GetObject to obtain version xxx of object object-test stored in bucket-test and change Content-Type to text/plain, then CanonicalizedResource would be as follows:

    /bucket-test/object-test?response-content-type=text/plain&versionId=xxx
    • bucket-name:

      If the bucket does not have a custom domain name associated, use its own name.

      Otherwise, use its associated custom domain name. In /obs.ccc.com/object, for example, obs.ccc.com is a custom bucket domain name.

      If an API operation does not require a bucket to be specified, for example, listing all buckets under an account, omit both the bucket name and object name by using, for example, /.

    • object-name:

      The name of the required object. Follow the object naming rules.

    • sub-resource: Arrange multiple sub-resources in ascending lexicographic order and use ampersands (&) to separate them.

      sub-resource identifiers: CDNNotifyConfiguration, acl, append, attname, backtosource, cors, customdomain, delete, deletebucket, directcoldaccess, encryption, inventory, length, lifecycle, location, logging, metadata, modify, name, notification, partNumber, policy, position, quota, rename, replication, restore, storageClass, storagePolicy, storageinfo, tagging, torrent, truncate, uploadId, uploads, versionId, versioning, versions, website,x-obs-security-token, object-lock, retention

      Response header sub-resources: response-cache-control, response-content-disposition, response-content-encoding, response-content-language, response-content-type, response-expires

      Image processing sub-resources: x-image-process, x-image-save-bucket, x-image-save-object

    Restrictions:

    A sub-resource usually has only one value. Listing multiple values for the same resource key—for example, key=value1&key=value2—is not recommended. If you do so, only the first sub-resource value is used to calculate the signature.

    Value range:

    None

    Default value:

    If this parameter is not specified, / is used.

  2. UTF-8 encode the result from step 1.
  3. Use your SK to calculate the HMAC-SHA1 of the result from step 2.
  4. Base64 encode the result from step 3 to obtain the signature.

Examples for Calculating Content-MD5 in Java

You can choose to add the Content-MD5 header when constructing the StringToSign. The code example below calculates the Content-MD5 header value.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import java.security.MessageDigest;
import sun.misc.BASE64Encoder;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;

public class Md5{
     public static void main(String[] args) {
          try {
                 String exampleString = "blog";
                 MessageDigest messageDigest = MessageDigest.getInstance("MD5"); 
                 BASE64Encoder encoder = new BASE64Encoder(); 
                 // Base64 encode the MD5 value of the string.
                 String contentMd5 = encoder.encode(messageDigest.digest(exampleString.getBytes("utf-8")));
                 System.out.println("Content-MD5:" + contentMd5);    
          } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) 
          {
                 e.printStackTrace();
          }
     }
}
Use hash-based message authentication code (HMAC) to calculate the signature based on the StringToSign and SK:
Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) )
The following is an example client request for creating a private bucket named newbucketname2 in a given region:
PUT / HTTP/1.1 
Host: newbucketname2.obs.region.myhuaweicloud.com
Content-Length: length
Date: Fri, 06 Jul 2018 03:45:51 GMT
x-obs-acl:private
x-obs-storage-class:STANDARD
Authorization: OBS UDSIAMSTUBTEST000254:ydH8ffpcbS6YpeOMcEZfn0wE90c=
<CreateBucketConfiguration xmlns="http://obs.region.myhuaweicloud.com/doc/2015-06-30/"> 
    <Location>region</Location> 
</CreateBucketConfiguration>

Code Examples

The signing code examples for different languages are as follows:

Java

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class SignDemo {
	
    private static final String SIGN_SEP = "\n";
    private static final String OBS_PREFIX = "x-obs-";
    private static final String DEFAULT_ENCODING = "UTF-8";
    private static final List<String> SUB_RESOURCES = Collections.unmodifiableList(Arrays.asList(
        "CDNNotifyConfiguration", "acl", "append", "attname", "backtosource", "cors", "customdomain", "delete",
   	"deletebucket", "directcoldaccess", "encryption", "inventory", "length", "lifecycle", "location", "logging",
   	"metadata", "mirrorBackToSource", "modify", "name", "notification", "obscompresspolicy", 
   	"partNumber", "policy", "position", "quota","rename", "replication", "response-cache-control", 
   	"response-content-disposition","response-content-encoding", "response-content-language", "response-content-type", 
   	"response-expires","restore", "storageClass", "storagePolicy", "storageinfo", "tagging", "torrent", "truncate",
   	"uploadId", "uploads", "versionId", "versioning", "versions", "website", "x-image-process",
  	 "x-image-save-bucket", "x-image-save-object", "x-obs-security-token", "object-lock", "retention"));
 
    private String ak;
    private String sk;
    // UTF8 encode the character string.
    public String urlEncode(String input) throws UnsupportedEncodingException {
        return URLEncoder.encode(input, DEFAULT_ENCODING)
            .replaceAll("%7E", "~") //for browser
            .replaceAll("%2F", "/")
            .replaceAll("%20", "+");
    }
 
    private String join(List<?> items, String delimiter) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < items.size(); i++) {
            String item = items.get(i).toString();
            sb.append(item);
            if (i < items.size() - 1) {
                sb.append(delimiter);
            }
        }
        return sb.toString();
    }
	
    private boolean isValid(String input) {
        return input != null && !input.equals("");
    }

    // Use the SK to calculate the HmacSHA1.
    public String hmacSha1(String input) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
        SecretKeySpec signingKey = new SecretKeySpec(this.sk.getBytes(DEFAULT_ENCODING), "HmacSHA1");
        // Obtain a Mac instance and use the getInstance method to specify the HMAC-SHA1 for the algorithm.
        Mac mac = Mac.getInstance("HmacSHA1");
        // Use the SK to initialize the Mac object.
        mac.init(signingKey);
        return Base64.getEncoder().encodeToString(mac.doFinal(input.getBytes(DEFAULT_ENCODING)));
    }

    // Construct a StringToSign.
    private String stringToSign(String httpMethod, Map<String, String[]> headers, Map<String, String> queries,
        String bucketName, String objectName) throws Exception{
        String contentMd5 = "";
        String contentType = "";
        String date = "";
		
        TreeMap<String, String> canonicalizedHeaders = new TreeMap<String, String>();
		
        String key;
        List<String> temp = new ArrayList<String>();
        for(Map.Entry<String, String[]> entry : headers.entrySet()) {
            key = entry.getKey();
            if(key == null || entry.getValue() == null || entry.getValue().length == 0) {
                continue;
            }
			
            key = key.trim().toLowerCase(Locale.ENGLISH);
            if(key.equals("content-md5")) {
                contentMd5 = entry.getValue()[0];
                continue;
            }
			
            if(key.equals("content-type")) {
                contentType = entry.getValue()[0];
                continue;
            }
			
            if(key.equals("date")) {
                date = entry.getValue()[0];
                continue;
            }
			
            if(key.startsWith(OBS_PREFIX)) {				
                for(String value : entry.getValue()) {
                    if(value != null) {
                        temp.add(value.trim());
                    }
                }
                canonicalizedHeaders.put(key, this.join(temp, ","));
                temp.clear();
            }
        }
	// If the header contains x-obs-date, leave the Date header blank.
        if(canonicalizedHeaders.containsKey("x-obs-date")) {
            date = "";
        }	
		
        // Construct the StringToSign by concatenating HTTP-Verb, Content-MD5, Content-Type, and Date.
        StringBuilder stringToSign = new StringBuilder();
        stringToSign.append(httpMethod).append(SIGN_SEP)
            .append(contentMd5).append(SIGN_SEP)
            .append(contentType).append(SIGN_SEP)
            .append(date).append(SIGN_SEP);
			
        // Construct the StringToSign by concatenating CanonicalizedHeaders.
        for(Map.Entry<String, String> entry : canonicalizedHeaders.entrySet()) {
            stringToSign.append(entry.getKey()).append(":").append(entry.getValue()).append(SIGN_SEP);
        }
		
        // Construct the StringToSign by concatenating CanonicalizedResource.
        stringToSign.append("/");
        if(this.isValid(bucketName)) {
            stringToSign.append(bucketName).append("/");
            if(this.isValid(objectName)) {
                stringToSign.append(this.urlEncode(objectName));
            }
        }
		
        TreeMap<String, String> canonicalizedResource = new TreeMap<String, String>();
        for(Map.Entry<String, String> entry : queries.entrySet()) {
            key = entry.getKey();
            if(key == null) {
                continue;
            }
			
            if(SUB_RESOURCES.contains(key)) {
                canonicalizedResource.put(key, entry.getValue());
            }
        }
		
        if(canonicalizedResource.size() > 0) {
            stringToSign.append("?");
            for(Map.Entry<String, String> entry : canonicalizedResource.entrySet()) {
                stringToSign.append(entry.getKey());
                if(this.isValid(entry.getValue())) {
                    stringToSign.append("=").append(entry.getValue());
                }
                stringToSign.append("&");
            }
            stringToSign.deleteCharAt(stringToSign.length()-1);
        }
		
        // System.out.println(String.format("StringToSign:%s%s", SIGN_SEP, stringToSign.toString()));
		
        return stringToSign.toString();
    }
	
    public String headerSignature(String httpMethod, Map<String, String[]> headers, Map<String, String> queries, String bucketName, String objectName) throws Exception {

    // Construct a StringToSign.
        String stringToSign = this.stringToSign(httpMethod, headers, queries, bucketName, objectName);
		
        // Calculate the signature.
        return String.format("OBS %s:%s", this.ak, this.hmacSha1(stringToSign));
    }
	
    public static void main(String[] args) throws Exception {
        SignDemo demo = new SignDemo();

        /* Hard-coded or plaintext AK and SK are risky. For security purposes, encrypt your AK and SK and store them in the configuration file or environment variables.
        In this example, the AK and SK are stored in environment variables for identity authentication. Before running the code in this example, configure environment variables HUAWEICLOUD_SDK_AK and HUAWEICLOUD_SDK_SK. */
        demo.ak = System.getenv("HUAWEICLOUD_SDK_AK");
        demo.sk = System.getenv("HUAWEICLOUD_SDK_SK");
		
        String bucketName = "bucket-test";
        String objectName = "hello.jpg";
        Map<String, String[]> headers = new HashMap<String, String[]>();
        headers.put("date", new String[] {"Sat, 12 Oct 2015 08:12:38 GMT"});
        headers.put("x-obs-acl", new String[] {"public-read"});
        headers.put("x-obs-meta-key1", new String[] {"value1"});
        headers.put("x-obs-meta-key2", new String[] {"value2", "value3"});
        Map<String, String> queries = new HashMap<String, String>();
        queries.put("acl", null);
	
        //Calculate and print the signature carried in the header.
        System.out.println(demo.headerSignature("PUT", headers, queries, bucketName, objectName));
    }
	
}

Python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import os
import sys
import hashlib
import hmac
import binascii
from datetime import datetime
IS_PYTHON2 = sys.version_info.major == 2 or sys.version < '3'

# Hard-coded or plaintext AK and SK are risky. For security purposes, encrypt your AK and SK and store them in the configuration file or environment variables.
# In this example, the AK and SK are stored in environment variables for identity authentication. Before running the code in this example, configure environment variables HUAWEICLOUD_SDK_AK and HUAWEICLOUD_SDK_SK.
yourSecretAccessKeyID = os.getenv('HUAWEICLOUD_SDK_SK')

httpMethod = "PUT"
contentType = "application/xml"
# "date" indicates when the request was generated.
date = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
canonicalizedHeaders = "x-obs-acl:private\n"
CanonicalizedResource = "/newbucketname2"

# Construct a StringToSign.
canonical_string = httpMethod + "\n" + "\n" + contentType + "\n" + date + "\n" + canonicalizedHeaders + CanonicalizedResource

# Calculate the signature using "Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) )"
if IS_PYTHON2:    
    hashed = hmac.new(yourSecretAccessKeyID, canonical_string, hashlib.sha1)    
    encode_canonical = binascii.b2a_base64(hashed.digest())[:-1]
else:    
    hashed = hmac.new(yourSecretAccessKeyID.encode('UTF-8'), canonical_string.encode('UTF-8'), hashlib.sha1)
    encode_canonical = binascii.b2a_base64(hashed.digest())[:-1].decode('UTF-8')

print(encode_canonical)

C

Download the sample code for calculating a signature with the C language.

  1. The API for calculating the signature is included in the sign.h header file.
  2. The example code for calculating the signature is included in the main.c file.

Addressing a Signature Mismatch

During an OBS API call, if the following error is reported:

Status code: 403 Forbidden

Error code: SignatureDoesNotMatch

Error message: The request signature we calculated does not match the signature you provided. Check your key and signing method.

Address the problem by referring to Why Don't the Signatures Match?

We use cookies to improve our site and your experience. By continuing to browse our site you accept our cookie policy. Find out more

Feedback

Feedback

Feedback

0/500

Selected Content

Submit selected content with the feedback