Skip to content

Commit 926b64a

Browse files
Add CloudSSO authentication support and related configurations (#1225)
* Add CloudSSO authentication support and related configurations * Enhance CloudSSO configuration handling and add related tests * Refactor CloudSSO functions for improved testability and add unit tests for configuration handling * Refactor configuration handling and enhance testing for profile management * Fix typo in error message for expired CloudSSO access token * Refactor configuration saving to use a variable function for improved testability * Skip external tests on Windows in TestProfile_GetCredential_External
1 parent 0a61728 commit 926b64a

28 files changed

+3614
-98
lines changed

README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ The following are supported authentication methods:
101101
| External | Use external processes to provide access credentials |
102102
| CredentialsURI | Use external services to provide access credentials |
103103
| ChainableRamRoleArn | Use chainable role assumption to provide access credentials |
104+
| CloudSSO | Use CloudSSO to provide access credentials |
104105

105106
If the --mode is not specified during configuration, the AK mode will be used by default.
106107

@@ -226,7 +227,7 @@ The Credentials URI must be response with status code 200, and following body:
226227
}
227228
```
228229

229-
Otherwise, CLI treate as failure case.
230+
Otherwise, CLI treat as failure case.
230231

231232
### Use OIDC to get credentials
232233

@@ -245,7 +246,20 @@ Default Language [zh|en] en:
245246
Saving profile[oidc_p] ...Done.
246247
```
247248

248-
### Enable bash/zsh auto completion
249+
### Use CloudSSO to get credentials
250+
251+
You can use the `--mode CloudSSO` to obtain credentials through CloudSSO. An example is as follows:
252+
253+
```shell
254+
$ aliyun configure --mode CloudSSO --profile cloud_sso
255+
Configuring profile 'cloudsso-test' in 'CloudSSO' authenticate mode...
256+
CloudSSO Sign In Url [https://signin-cn-shanghai.alibabacloudsso.com/start/login]:
257+
# CloudSSO Sign In Url is required, please input it.
258+
# then follow the instructions to sign in.
259+
```
260+
261+
262+
### Enable bash/zsh auto-completion
249263

250264
- Use `aliyun auto-completion` command to enable auto completion in zsh/bash
251265
- Use `aliyun auto-completion --uninstall` command to disable auto completion.
@@ -431,6 +445,7 @@ We support the following environment variables:
431445
- `ALIBABA_CLOUD_ACCESS_KEY_SECRET`: When no Access Key Secret is specified, the CLI uses it.
432446
- `ALIBABA_CLOUD_SECURITY_TOKEN`: When no Security Token is specified, the CLI uses it.
433447
- `ALIBABA_CLOUD_REGION_ID`: When no Region Id is specified, the CLI uses it.
448+
- `ALIBABA_CLOUD_SSO_CLIENT_ID`: Use this variable to override the client ID of the SSO application.
434449
- `DEBUG=sdk`:Through this variable, the CLI can display HTTP request information, which is helpful for troubleshooting.
435450
436451
## Getting Help

cli/version.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14+
1415
package cli
1516

1617
import (

cloudsso/access_configurations.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package cloudsso
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/aliyun/aliyun-cli/v3/cli"
7+
"io"
8+
"net/http"
9+
"strconv"
10+
)
11+
12+
// AccessConfigurationsParameter is a struct that holds the parameters for accessing configurations.
13+
type AccessConfigurationsParameter struct {
14+
UrlPrefix string `json:"urlPrefix"`
15+
AccessToken string `json:"accessToken"`
16+
AccountId string `json:"accountId"`
17+
HttpClient *http.Client `json:"-"`
18+
}
19+
20+
// AccessConfigurationsRequest 表示获取访问配置的请求参数
21+
type AccessConfigurationsRequest struct {
22+
AccountId string
23+
NextToken string
24+
MaxResults int
25+
}
26+
27+
// AccessConfigurationsResponse 表示访问配置的响应
28+
type AccessConfigurationsResponse struct {
29+
AccessConfigurationsForAccount []AccessConfiguration `json:"AccessConfigurationsForAccount"`
30+
NextToken string `json:"NextToken"`
31+
IsTruncated bool `json:"IsTruncated"`
32+
}
33+
34+
// AccessConfiguration 表示单个访问配置
35+
type AccessConfiguration struct {
36+
AccessConfigurationId string `json:"AccessConfigurationId"`
37+
AccessConfigurationName string `json:"AccessConfigurationName"`
38+
AccessConfigurationDescription string `json:"AccessConfigurationDescription"`
39+
}
40+
41+
// ListAccessConfigurationsForAccount 获取单次访问配置列表
42+
func (p *AccessConfigurationsParameter) ListAccessConfigurationsForAccount(req AccessConfigurationsRequest) (*AccessConfigurationsResponse, error) {
43+
// 构建URL
44+
url := fmt.Sprintf("%s/access-assignments/access-configurations", p.UrlPrefix)
45+
46+
// 添加查询参数
47+
query := url + "?AccountId=" + req.AccountId
48+
if req.NextToken != "" {
49+
query += "&NextToken=" + req.NextToken
50+
}
51+
if req.MaxResults > 0 {
52+
query += "&MaxResults=" + strconv.Itoa(req.MaxResults)
53+
}
54+
55+
// 创建HTTP请求
56+
httpReq, err := http.NewRequest("GET", query, nil)
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
// 设置请求头
62+
httpReq.Header.Set("Accept", "application/json")
63+
httpReq.Header.Set("Content-Type", "application/json")
64+
httpReq.Header.Set("Authorization", "Bearer "+p.AccessToken)
65+
httpReq.Header.Set("User-Agent", "aliyun/CLI-"+cli.Version)
66+
67+
// 发送请求
68+
client := p.HttpClient
69+
if client == nil {
70+
client = http.DefaultClient
71+
}
72+
73+
resp, err := client.Do(httpReq)
74+
if err != nil {
75+
return nil, err
76+
}
77+
defer resp.Body.Close()
78+
79+
// 读取响应体
80+
body, err := io.ReadAll(resp.Body)
81+
if err != nil {
82+
return nil, err
83+
}
84+
85+
// 检查错误
86+
if resp.StatusCode >= 400 && resp.StatusCode < 500 {
87+
var errResp struct {
88+
ErrorCode string `json:"ErrorCode"`
89+
ErrorMessage string `json:"ErrorMessage"`
90+
RequestId string `json:"RequestId"`
91+
}
92+
93+
if err := json.Unmarshal(body, &errResp); err != nil {
94+
return nil, err
95+
}
96+
97+
return nil, fmt.Errorf("%s: %s %s", errResp.ErrorCode, errResp.ErrorMessage, errResp.RequestId)
98+
}
99+
100+
// 解析响应
101+
var result AccessConfigurationsResponse
102+
if err := json.Unmarshal(body, &result); err != nil {
103+
return nil, err
104+
}
105+
106+
return &result, nil
107+
}
108+
109+
// ListAllAccessConfigurations 获取所有访问配置列表
110+
func (p *AccessConfigurationsParameter) ListAllAccessConfigurations(req AccessConfigurationsRequest) ([]AccessConfiguration, error) {
111+
var configurations []AccessConfiguration
112+
113+
// 获取第一页数据
114+
response, err := p.ListAccessConfigurationsForAccount(req)
115+
if err != nil {
116+
return nil, err
117+
}
118+
119+
configurations = append(configurations, response.AccessConfigurationsForAccount...)
120+
121+
// 如果有更多页,继续请求
122+
for response.IsTruncated {
123+
req.NextToken = response.NextToken
124+
125+
response, err = p.ListAccessConfigurationsForAccount(req)
126+
if err != nil {
127+
return nil, err
128+
}
129+
130+
configurations = append(configurations, response.AccessConfigurationsForAccount...)
131+
}
132+
133+
return configurations, nil
134+
}

0 commit comments

Comments
 (0)