Skip to content

Commit f48178a

Browse files
committed
kodofs/kodoutil
1 parent 46b7ddd commit f48178a

25 files changed

+3403
-0
lines changed

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/xushiwei/kodofs
2+
3+
go 1.16
4+
5+
require golang.org/x/sync v0.3.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
2+
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=

internal/kodo/api/api.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package api
2+
3+
import (
4+
"io"
5+
"net/http"
6+
)
7+
8+
// -----------------------------------------------------------------------------------------
9+
10+
// BytesFromRequest 读取http.Request.Body的内容到slice中
11+
func BytesFromRequest(r *http.Request) (b []byte, err error) {
12+
if r.ContentLength == 0 {
13+
return
14+
}
15+
if r.ContentLength > 0 {
16+
b = make([]byte, int(r.ContentLength))
17+
_, err = io.ReadFull(r.Body, b)
18+
return
19+
}
20+
return io.ReadAll(r.Body)
21+
}
22+
23+
// -----------------------------------------------------------------------------------------
24+
25+
// 可以根据Code判断是何种类型错误
26+
type QError struct {
27+
Code string
28+
Message string
29+
}
30+
31+
// Error 继承error接口
32+
func (e *QError) Error() string {
33+
return e.Code + ": " + e.Message
34+
}
35+
36+
// NewError 返回QError指针
37+
func NewError(code, message string) *QError {
38+
return &QError{
39+
Code: code,
40+
Message: message,
41+
}
42+
}
43+
44+
// -----------------------------------------------------------------------------------------

internal/kodo/auth/auth.go

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
package auth
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"crypto/hmac"
7+
"crypto/sha1"
8+
"encoding/base64"
9+
"fmt"
10+
"io"
11+
"net/http"
12+
"net/textproto"
13+
"sort"
14+
"strings"
15+
16+
"github.com/xushiwei/kodofs/internal/kodo/api"
17+
"github.com/xushiwei/kodofs/internal/kodo/conf"
18+
)
19+
20+
// 七牛签名算法的类型:
21+
// QBoxToken, QiniuToken, BearToken, QiniuMacToken
22+
type TokenType int
23+
24+
const (
25+
TokenQiniu TokenType = iota
26+
TokenQBox
27+
)
28+
29+
const (
30+
IAMKeyLen = 33
31+
IAMKeyPrefix = "IAM-"
32+
33+
AuthorizationPrefixQiniu = "Qiniu "
34+
AuthorizationPrefixQBox = "QBox "
35+
)
36+
37+
// -----------------------------------------------------------------------------------------
38+
39+
// AK/SK可以从 https://portal.qiniu.com/user/key 获取
40+
type Credentials struct {
41+
AccessKey string
42+
SecretKey []byte
43+
}
44+
45+
// 构建一个Credentials对象
46+
func New(accessKey, secretKey string) *Credentials {
47+
return &Credentials{accessKey, []byte(secretKey)}
48+
}
49+
50+
// Sign 对数据进行签名,一般用于私有空间下载用途
51+
func (ath *Credentials) Sign(data []byte) (token string) {
52+
h := hmac.New(sha1.New, ath.SecretKey)
53+
h.Write(data)
54+
55+
sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
56+
return fmt.Sprintf("%s:%s", ath.AccessKey, sign)
57+
}
58+
59+
// SignWithData 对数据进行签名,一般用于上传凭证的生成用途
60+
func (ath *Credentials) SignWithData(b []byte) (token string) {
61+
encodedData := base64.URLEncoding.EncodeToString(b)
62+
sign := ath.Sign([]byte(encodedData))
63+
return fmt.Sprintf("%s:%s", sign, encodedData)
64+
}
65+
66+
// SignToken 根据t的类型对请求进行签名,并把token加入req中
67+
func (ath *Credentials) AddToken(t TokenType, req *http.Request) error {
68+
switch t {
69+
case TokenQiniu:
70+
token, sErr := ath.SignRequestV2(req)
71+
if sErr != nil {
72+
return sErr
73+
}
74+
req.Header.Add("Authorization", AuthorizationPrefixQiniu+token)
75+
default:
76+
token, err := ath.SignRequest(req)
77+
if err != nil {
78+
return err
79+
}
80+
req.Header.Add("Authorization", AuthorizationPrefixQBox+token)
81+
}
82+
return nil
83+
}
84+
85+
// SignRequest 对数据进行签名,一般用于管理凭证的生成
86+
func (ath *Credentials) SignRequest(req *http.Request) (token string, err error) {
87+
data, err := collectData(req)
88+
if err != nil {
89+
return
90+
}
91+
token = ath.Sign(data)
92+
return
93+
}
94+
95+
// SignRequestV2 对数据进行签名,一般用于高级管理凭证的生成
96+
func (ath *Credentials) SignRequestV2(req *http.Request) (token string, err error) {
97+
98+
data, err := collectDataV2(req)
99+
if err != nil {
100+
return
101+
}
102+
token = ath.Sign(data)
103+
return
104+
}
105+
106+
type (
107+
xQiniuHeaderItem struct {
108+
HeaderName string
109+
HeaderValue string
110+
}
111+
xQiniuHeaders []xQiniuHeaderItem
112+
)
113+
114+
func (headers xQiniuHeaders) Len() int {
115+
return len(headers)
116+
}
117+
118+
func (headers xQiniuHeaders) Less(i, j int) bool {
119+
if headers[i].HeaderName < headers[j].HeaderName {
120+
return true
121+
} else if headers[i].HeaderName > headers[j].HeaderName {
122+
return false
123+
} else {
124+
return headers[i].HeaderValue < headers[j].HeaderValue
125+
}
126+
}
127+
128+
func (headers xQiniuHeaders) Swap(i, j int) {
129+
headers[i], headers[j] = headers[j], headers[i]
130+
}
131+
132+
func collectDataV2(req *http.Request) (data []byte, err error) {
133+
u := req.URL
134+
135+
//write method path?query
136+
s := fmt.Sprintf("%s %s", req.Method, u.Path)
137+
if u.RawQuery != "" {
138+
s += "?"
139+
s += u.RawQuery
140+
}
141+
142+
//write host and post
143+
s += "\nHost: " + req.Host + "\n"
144+
145+
//write content type
146+
contentType := req.Header.Get("Content-Type")
147+
if contentType == "" {
148+
contentType = "application/x-www-form-urlencoded"
149+
req.Header.Set("Content-Type", contentType)
150+
}
151+
s += fmt.Sprintf("Content-Type: %s\n", contentType)
152+
153+
xQiniuHeaders := make(xQiniuHeaders, 0, len(req.Header))
154+
for headerName := range req.Header {
155+
if len(headerName) > len("X-Qiniu-") && strings.HasPrefix(headerName, "X-Qiniu-") {
156+
xQiniuHeaders = append(xQiniuHeaders, xQiniuHeaderItem{
157+
HeaderName: textproto.CanonicalMIMEHeaderKey(headerName),
158+
HeaderValue: req.Header.Get(headerName),
159+
})
160+
}
161+
}
162+
163+
if len(xQiniuHeaders) > 0 {
164+
sort.Sort(xQiniuHeaders)
165+
for _, xQiniuHeader := range xQiniuHeaders {
166+
s += fmt.Sprintf("%s: %s\n", xQiniuHeader.HeaderName, xQiniuHeader.HeaderValue)
167+
}
168+
}
169+
s += "\n"
170+
171+
data = []byte(s)
172+
//write body
173+
if incBodyV2(req) {
174+
s2, rErr := api.BytesFromRequest(req)
175+
if rErr != nil {
176+
err = rErr
177+
return
178+
}
179+
req.Body = io.NopCloser(bytes.NewReader(s2))
180+
data = append(data, s2...)
181+
}
182+
return
183+
}
184+
185+
func collectData(req *http.Request) (data []byte, err error) {
186+
u := req.URL
187+
s := u.Path
188+
if u.RawQuery != "" {
189+
s += "?"
190+
s += u.RawQuery
191+
}
192+
s += "\n"
193+
194+
data = []byte(s)
195+
if incBody(req) {
196+
s2, rErr := api.BytesFromRequest(req)
197+
if rErr != nil {
198+
err = rErr
199+
return
200+
}
201+
req.Body = io.NopCloser(bytes.NewReader(s2))
202+
data = append(data, s2...)
203+
}
204+
return
205+
}
206+
207+
// 管理凭证生成时,是否同时对request body进行签名
208+
func incBody(req *http.Request) bool {
209+
return req.Body != nil && req.Header.Get("Content-Type") == conf.CONTENT_TYPE_FORM
210+
}
211+
212+
func incBodyV2(req *http.Request) bool {
213+
contentType := req.Header.Get("Content-Type")
214+
return req.Body != nil && (contentType == conf.CONTENT_TYPE_FORM || contentType == conf.CONTENT_TYPE_JSON)
215+
}
216+
217+
// -----------------------------------------------------------------------------------------
218+
219+
// MacContextKey 是用户的密钥信息
220+
// context.Context中的键值不应该使用普通的字符串, 有可能导致命名冲突
221+
type macContextKey struct{}
222+
223+
// tokenTypeKey 是签名算法类型key
224+
type tokenTypeKey struct{}
225+
226+
// WithCredentials 返回一个包含密钥信息的context
227+
func WithCredentials(ctx context.Context, cred *Credentials) context.Context {
228+
if ctx == nil {
229+
ctx = context.Background()
230+
}
231+
return context.WithValue(ctx, macContextKey{}, cred)
232+
}
233+
234+
// WithCredentialsType 返回一个context, 保存了密钥信息和token类型
235+
func WithCredentialsType(ctx context.Context, cred *Credentials, t TokenType) context.Context {
236+
ctx = WithCredentials(ctx, cred)
237+
return context.WithValue(ctx, tokenTypeKey{}, t)
238+
}
239+
240+
// CredentialsFromContext 从context获取密钥信息
241+
func CredentialsFromContext(ctx context.Context) (cred *Credentials, t TokenType, ok bool) {
242+
cred, ok = ctx.Value(macContextKey{}).(*Credentials)
243+
t, yes := ctx.Value(tokenTypeKey{}).(TokenType)
244+
if !yes {
245+
t = TokenQBox
246+
}
247+
return
248+
}
249+
250+
// -----------------------------------------------------------------------------------------

0 commit comments

Comments
 (0)