1/*
2 * Licensed under the Apache License, Version 2.0 (the "License");
3 * you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at
5 *
6 * http://www.apache.org/licenses/LICENSE-2.0
7 *
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
13 */
14
15package signers
16
17import (
18	"encoding/json"
19	"fmt"
20	"net/http"
21	"strconv"
22	"time"
23
24	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
25	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
26	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
27	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
28	jmespath "github.com/jmespath/go-jmespath"
29)
30
31const (
32	defaultDurationSeconds = 3600
33)
34
35type RamRoleArnSigner struct {
36	*credentialUpdater
37	roleSessionName   string
38	sessionCredential *SessionCredential
39	credential        *credentials.RamRoleArnCredential
40	commonApi         func(request *requests.CommonRequest, signer interface{}) (response *responses.CommonResponse, err error)
41}
42
43func NewRamRoleArnSigner(credential *credentials.RamRoleArnCredential, commonApi func(request *requests.CommonRequest, signer interface{}) (response *responses.CommonResponse, err error)) (signer *RamRoleArnSigner, err error) {
44	signer = &RamRoleArnSigner{
45		credential: credential,
46		commonApi:  commonApi,
47	}
48
49	signer.credentialUpdater = &credentialUpdater{
50		credentialExpiration: credential.RoleSessionExpiration,
51		buildRequestMethod:   signer.buildCommonRequest,
52		responseCallBack:     signer.refreshCredential,
53		refreshApi:           signer.refreshApi,
54	}
55
56	if len(credential.RoleSessionName) > 0 {
57		signer.roleSessionName = credential.RoleSessionName
58	} else {
59		signer.roleSessionName = "aliyun-go-sdk-" + strconv.FormatInt(time.Now().UnixNano()/1000, 10)
60	}
61	if credential.RoleSessionExpiration > 0 {
62		if credential.RoleSessionExpiration >= 900 && credential.RoleSessionExpiration <= 3600 {
63			signer.credentialExpiration = credential.RoleSessionExpiration
64		} else {
65			err = errors.NewClientError(errors.InvalidParamErrorCode, "Assume Role session duration should be in the range of 15min - 1Hr", nil)
66		}
67	} else {
68		signer.credentialExpiration = defaultDurationSeconds
69	}
70	return
71}
72
73func (*RamRoleArnSigner) GetName() string {
74	return "HMAC-SHA1"
75}
76
77func (*RamRoleArnSigner) GetType() string {
78	return ""
79}
80
81func (*RamRoleArnSigner) GetVersion() string {
82	return "1.0"
83}
84
85func (signer *RamRoleArnSigner) GetAccessKeyId() (accessKeyId string, err error) {
86	if signer.sessionCredential == nil || signer.needUpdateCredential() {
87		err = signer.updateCredential()
88		if err != nil {
89			return
90		}
91	}
92
93	if signer.sessionCredential == nil || len(signer.sessionCredential.AccessKeyId) <= 0 {
94		return "", err
95	}
96
97	return signer.sessionCredential.AccessKeyId, nil
98}
99
100func (signer *RamRoleArnSigner) GetExtraParam() map[string]string {
101	if signer.sessionCredential == nil || signer.needUpdateCredential() {
102		signer.updateCredential()
103	}
104	if signer.sessionCredential == nil || len(signer.sessionCredential.StsToken) <= 0 {
105		return make(map[string]string)
106	}
107	return map[string]string{"SecurityToken": signer.sessionCredential.StsToken}
108}
109
110func (signer *RamRoleArnSigner) Sign(stringToSign, secretSuffix string) string {
111	secret := signer.sessionCredential.AccessKeySecret + secretSuffix
112	return ShaHmac1(stringToSign, secret)
113}
114
115func (signer *RamRoleArnSigner) buildCommonRequest() (request *requests.CommonRequest, err error) {
116	request = requests.NewCommonRequest()
117	if signer.credential.StsRegion != "" {
118		request.Domain = fmt.Sprintf("sts.%s.aliyuncs.com", signer.credential.StsRegion)
119	} else {
120		request.Domain = "sts.aliyuncs.com"
121	}
122	request.Product = "Sts"
123	request.Version = "2015-04-01"
124	request.ApiName = "AssumeRole"
125	request.Scheme = requests.HTTPS
126	request.QueryParams["RoleArn"] = signer.credential.RoleArn
127	if signer.credential.Policy != "" {
128		request.QueryParams["Policy"] = signer.credential.Policy
129	}
130	request.QueryParams["RoleSessionName"] = signer.credential.RoleSessionName
131	request.QueryParams["DurationSeconds"] = strconv.Itoa(signer.credentialExpiration)
132	return
133}
134
135func (signer *RamRoleArnSigner) refreshApi(request *requests.CommonRequest) (response *responses.CommonResponse, err error) {
136	credential := &credentials.AccessKeyCredential{
137		AccessKeyId:     signer.credential.AccessKeyId,
138		AccessKeySecret: signer.credential.AccessKeySecret,
139	}
140	signerV1 := NewAccessKeySigner(credential)
141	return signer.commonApi(request, signerV1)
142}
143
144func (signer *RamRoleArnSigner) refreshCredential(response *responses.CommonResponse) (err error) {
145	if response.GetHttpStatus() != http.StatusOK {
146		message := "refresh session token failed"
147		err = errors.NewServerError(response.GetHttpStatus(), response.GetHttpContentString(), message)
148		return
149	}
150	var data interface{}
151	err = json.Unmarshal(response.GetHttpContentBytes(), &data)
152	if err != nil {
153		return fmt.Errorf("refresh RoleArn sts token err, json.Unmarshal fail: %s", err.Error())
154	}
155	accessKeyId, err := jmespath.Search("Credentials.AccessKeyId", data)
156	if err != nil {
157		return fmt.Errorf("refresh RoleArn sts token err, fail to get AccessKeyId: %s", err.Error())
158	}
159	accessKeySecret, err := jmespath.Search("Credentials.AccessKeySecret", data)
160	if err != nil {
161		return fmt.Errorf("refresh RoleArn sts token err, fail to get AccessKeySecret: %s", err.Error())
162	}
163	securityToken, err := jmespath.Search("Credentials.SecurityToken", data)
164	if err != nil {
165		return fmt.Errorf("refresh RoleArn sts token err, fail to get SecurityToken: %s", err.Error())
166	}
167	if accessKeyId == nil || accessKeySecret == nil || securityToken == nil {
168		return
169	}
170	signer.sessionCredential = &SessionCredential{
171		AccessKeyId:     accessKeyId.(string),
172		AccessKeySecret: accessKeySecret.(string),
173		StsToken:        securityToken.(string),
174	}
175	return
176}
177
178func (signer *RamRoleArnSigner) GetSessionCredential() *SessionCredential {
179	return signer.sessionCredential
180}
181