gitee.com/larksuite/oapi-sdk-go/v3@v3.0.3/core/tokenmanager.go (about)

     1  /*
     2   * MIT License
     3   *
     4   * Copyright (c) 2022 Lark Technologies Pte. Ltd.
     5   *
     6   * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
     7   *
     8   * The above copyright notice and this permission notice, shall be included in all copies or substantial portions of the Software.
     9   *
    10   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    11   */
    12  
    13  package larkcore
    14  
    15  import (
    16  	"context"
    17  	"encoding/json"
    18  	"errors"
    19  	"fmt"
    20  	"net/http"
    21  	"time"
    22  )
    23  
    24  var tokenManager TokenManager = TokenManager{cache: cache}
    25  
    26  type TokenManager struct {
    27  	cache Cache
    28  }
    29  
    30  func (m *TokenManager) getAppAccessToken(ctx context.Context, config *Config, appTicket string) (string, error) {
    31  	token, err := m.get(ctx, appAccessTokenKey(config.AppId))
    32  	if err != nil {
    33  		return "", err
    34  	}
    35  
    36  	appType := config.AppType
    37  	if token == "" {
    38  		if appType == AppTypeSelfBuilt {
    39  			token, err = m.getCustomAppAccessTokenThenCache(ctx, config)
    40  			if err != nil {
    41  				return "", err
    42  			}
    43  			return token, nil
    44  		} else {
    45  			token, err = m.getMarketplaceAppAccessTokenThenCache(ctx, config, appTicket)
    46  			if err != nil {
    47  				return "", err
    48  			}
    49  			return token, nil
    50  		}
    51  	}
    52  	return token, nil
    53  }
    54  
    55  func (m *TokenManager) getTenantAccessToken(ctx context.Context, config *Config, tenantKey, appTicket string) (string, error) {
    56  	token, err := m.get(ctx, tenantAccessTokenKey(config.AppId, tenantKey))
    57  	if err != nil {
    58  		return "", err
    59  	}
    60  
    61  	if token == "" {
    62  		if config.AppType == AppTypeSelfBuilt {
    63  			token, err = m.getCustomTenantAccessTokenThenCache(ctx, config, tenantKey)
    64  			if err != nil {
    65  				return "", err
    66  			}
    67  			return token, nil
    68  		} else {
    69  			token, err = m.getMarketplaceTenantAccessTokenThenCache(ctx, config, tenantKey, appTicket)
    70  			if err != nil {
    71  				return "", err
    72  			}
    73  			return token, nil
    74  		}
    75  	}
    76  	return token, nil
    77  }
    78  
    79  func (m *TokenManager) set(ctx context.Context, key, value string, ttl time.Duration) error {
    80  	return m.cache.Set(ctx, key, value, ttl)
    81  }
    82  
    83  func (m *TokenManager) get(ctx context.Context, tokenKey string) (string, error) {
    84  	token, err := m.cache.Get(ctx, tokenKey)
    85  	return token, err
    86  }
    87  
    88  type SelfBuiltAppAccessTokenReq struct {
    89  	AppID     string `json:"app_id"`
    90  	AppSecret string `json:"app_secret"`
    91  }
    92  
    93  type SelfBuiltTenantAccessTokenReq struct {
    94  	AppID     string `json:"app_id"`
    95  	AppSecret string `json:"app_secret"`
    96  }
    97  type AppAccessTokenResp struct {
    98  	*ApiResp `json:"-"`
    99  	CodeError
   100  	Expire         int    `json:"expire"`
   101  	AppAccessToken string `json:"app_access_token"`
   102  }
   103  
   104  func (t *AppAccessTokenResp) Success() bool {
   105  	return t.Code == 0
   106  }
   107  
   108  type TenantAccessTokenResp struct {
   109  	*ApiResp `json:"-"`
   110  	CodeError
   111  	Expire            int    `json:"expire"`
   112  	TenantAccessToken string `json:"tenant_access_token"`
   113  }
   114  
   115  func (t *TenantAccessTokenResp) Success() bool {
   116  	return t.Code == 0
   117  }
   118  
   119  type MarketplaceAppAccessTokenReq struct {
   120  	AppID     string `json:"app_id"`
   121  	AppSecret string `json:"app_secret"`
   122  	AppTicket string `json:"app_ticket"`
   123  }
   124  
   125  type MarketplaceTenantAccessTokenReq struct {
   126  	AppAccessToken string `json:"app_access_token"`
   127  	TenantKey      string `json:"tenant_key"`
   128  }
   129  
   130  func appAccessTokenKey(appID string) string {
   131  	return fmt.Sprintf("%s-%s", appAccessTokenKeyPrefix, appID)
   132  }
   133  
   134  func tenantAccessTokenKey(appID, tenantKey string) string {
   135  	return fmt.Sprintf("%s-%s-%s", tenantAccessTokenKeyPrefix, appID, tenantKey)
   136  }
   137  func (m *TokenManager) getCustomAppAccessTokenThenCache(ctx context.Context, config *Config) (string, error) {
   138  	rawResp, err := Request(ctx, &ApiReq{
   139  		HttpMethod: http.MethodPost,
   140  		ApiPath:    AppAccessTokenInternalUrlPath,
   141  		Body: &SelfBuiltAppAccessTokenReq{
   142  			AppID:     config.AppId,
   143  			AppSecret: config.AppSecret,
   144  		},
   145  		SupportedAccessTokenTypes: []AccessTokenType{AccessTokenTypeNone},
   146  	}, config)
   147  
   148  	if err != nil {
   149  		return "", err
   150  	}
   151  	resp := &AppAccessTokenResp{}
   152  	err = json.Unmarshal(rawResp.RawBody, resp)
   153  	if err != nil {
   154  		return "", err
   155  	}
   156  	if resp.Code != 0 {
   157  		config.Logger.Warn(ctx, fmt.Sprintf("custom app appAccessToken cache, err:%v", Prettify(resp)))
   158  		return "", resp.CodeError
   159  	}
   160  	expire := time.Duration(resp.Expire)*time.Second - expiryDelta
   161  	err = m.set(ctx, appAccessTokenKey(config.AppId), resp.AppAccessToken, expire)
   162  	if err != nil {
   163  		config.Logger.Warn(ctx, fmt.Sprintf("custom app appAccessToken save cache, err:%v", err))
   164  	}
   165  	return resp.AppAccessToken, err
   166  }
   167  
   168  func (m *TokenManager) getCustomTenantAccessTokenThenCache(ctx context.Context, config *Config, tenantKey string) (string, error) {
   169  	rawResp, err := Request(ctx, &ApiReq{
   170  		HttpMethod: http.MethodPost,
   171  		ApiPath:    TenantAccessTokenInternalUrlPath,
   172  		Body: &SelfBuiltAppAccessTokenReq{
   173  			AppID:     config.AppId,
   174  			AppSecret: config.AppSecret,
   175  		},
   176  		SupportedAccessTokenTypes: []AccessTokenType{AccessTokenTypeNone},
   177  	}, config)
   178  
   179  	if err != nil {
   180  		return "", err
   181  	}
   182  	tenantAccessTokenResp := &TenantAccessTokenResp{}
   183  	err = json.Unmarshal(rawResp.RawBody, tenantAccessTokenResp)
   184  	if err != nil {
   185  		return "", err
   186  	}
   187  	if tenantAccessTokenResp.Code != 0 {
   188  		config.Logger.Warn(ctx, fmt.Sprintf("custom app tenantAccessToken cache, err:%v", Prettify(tenantAccessTokenResp)))
   189  		return "", tenantAccessTokenResp.CodeError
   190  	}
   191  	expire := time.Duration(tenantAccessTokenResp.Expire)*time.Second - expiryDelta
   192  	err = m.cache.Set(ctx, tenantAccessTokenKey(config.AppId, tenantKey), tenantAccessTokenResp.TenantAccessToken, expire)
   193  	if err != nil {
   194  		config.Logger.Warn(ctx, fmt.Sprintf("custom app tenantAccessToken save cache, err:%v", err))
   195  	}
   196  	return tenantAccessTokenResp.TenantAccessToken, err
   197  }
   198  
   199  var ErrAppTicketIsEmpty = errors.New("app ticket is empty")
   200  
   201  func (m *TokenManager) getMarketplaceAppAccessTokenThenCache(ctx context.Context, config *Config, appTicket string) (string, error) {
   202  	if appTicket == "" {
   203  		appTicket1, err := appTicketManager.Get(ctx, config)
   204  		if err != nil {
   205  			return "", err
   206  		}
   207  		if appTicket1 == "" {
   208  			return "", ErrAppTicketIsEmpty
   209  		}
   210  		appTicket = appTicket1
   211  	}
   212  	rawResp, err := Request(ctx, &ApiReq{
   213  		HttpMethod: http.MethodPost,
   214  		ApiPath:    AppAccessTokenUrlPath,
   215  		Body: &MarketplaceAppAccessTokenReq{
   216  			AppID:     config.AppId,
   217  			AppSecret: config.AppSecret,
   218  			AppTicket: appTicket,
   219  		},
   220  		SupportedAccessTokenTypes: []AccessTokenType{AccessTokenTypeNone},
   221  	}, config)
   222  
   223  	if err != nil {
   224  		return "", err
   225  	}
   226  	appAccessTokenResp := &AppAccessTokenResp{}
   227  	err = json.Unmarshal(rawResp.RawBody, appAccessTokenResp)
   228  	if err != nil {
   229  		config.Logger.Warn(ctx, fmt.Sprintf("marketplace app appAccessToken cache, err:%v", Prettify(appAccessTokenResp)))
   230  		return "", err
   231  	}
   232  	if appAccessTokenResp.Code != 0 {
   233  		return "", appAccessTokenResp.CodeError
   234  	}
   235  	expire := time.Duration(appAccessTokenResp.Expire)*time.Second - expiryDelta
   236  	err = m.set(ctx, appAccessTokenKey(config.AppId), appAccessTokenResp.AppAccessToken, expire)
   237  	if err != nil {
   238  		config.Logger.Warn(ctx, fmt.Sprintf("marketplace app appAccessToken save cache, err:%v", err))
   239  	}
   240  	return appAccessTokenResp.AppAccessToken, err
   241  }
   242  
   243  // get marketplace tenant access token
   244  func (m *TokenManager) getMarketplaceTenantAccessTokenThenCache(ctx context.Context, config *Config, tenantKey, appTicket string) (string, error) {
   245  	appAccessToken, err := m.getMarketplaceAppAccessTokenThenCache(ctx, config, appTicket)
   246  	if err != nil {
   247  		return "", err
   248  	}
   249  	rawResp, err := Request(ctx, &ApiReq{
   250  		HttpMethod: http.MethodPost,
   251  		ApiPath:    TenantAccessTokenUrlPath,
   252  		Body: &MarketplaceTenantAccessTokenReq{
   253  			AppAccessToken: appAccessToken,
   254  			TenantKey:      tenantKey,
   255  		},
   256  		SupportedAccessTokenTypes: []AccessTokenType{AccessTokenTypeNone},
   257  	}, config)
   258  
   259  	if err != nil {
   260  		return "", err
   261  	}
   262  	tenantAccessTokenResp := &TenantAccessTokenResp{}
   263  	err = json.Unmarshal(rawResp.RawBody, tenantAccessTokenResp)
   264  	if err != nil {
   265  		config.Logger.Warn(ctx, fmt.Sprintf("marketplace app tenantAccessToken cache, err:%v", Prettify(tenantAccessTokenResp)))
   266  		return "", err
   267  	}
   268  	if tenantAccessTokenResp.Code != 0 {
   269  		return "", tenantAccessTokenResp.CodeError
   270  	}
   271  	expire := time.Duration(tenantAccessTokenResp.Expire)*time.Second - expiryDelta
   272  	err = m.set(ctx, tenantAccessTokenKey(config.AppId, tenantKey), tenantAccessTokenResp.TenantAccessToken, expire)
   273  	if err != nil {
   274  		config.Logger.Warn(ctx, fmt.Sprintf("market app tenantAccessToken save cache, err:%v", err))
   275  	}
   276  	return tenantAccessTokenResp.TenantAccessToken, err
   277  }