gitee.com/larksuite/oapi-sdk-go/v3@v3.0.3/card/card.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 larkcard
    14  
    15  import (
    16  	"context"
    17  	"crypto/sha1"
    18  	"encoding/json"
    19  	"errors"
    20  	"fmt"
    21  	"net/http"
    22  	"strings"
    23  
    24  	larkcore "gitee.com/larksuite/oapi-sdk-go/v3/core"
    25  	larkevent "gitee.com/larksuite/oapi-sdk-go/v3/event"
    26  )
    27  
    28  type CardActionHandler struct {
    29  	verificationToken string
    30  	eventEncryptKey   string
    31  	handler           func(context.Context, *CardAction) (interface{}, error)
    32  	*larkcore.Config
    33  }
    34  
    35  func processError(ctx context.Context, logger larkcore.Logger, path string, err error) *larkevent.EventResp {
    36  	header := map[string][]string{}
    37  	statusCode := http.StatusInternalServerError
    38  	header[larkevent.ContentTypeHeader] = []string{larkevent.DefaultContentType}
    39  	eventResp := &larkevent.EventResp{
    40  		Header:     header,
    41  		Body:       []byte(fmt.Sprintf(larkevent.WebhookResponseFormat, err.Error())),
    42  		StatusCode: statusCode,
    43  	}
    44  	logger.Error(ctx, fmt.Sprintf("handle cardAcion,path:%s, err: %v", path, err))
    45  	return eventResp
    46  }
    47  
    48  func recoveryResult() *larkevent.EventResp {
    49  	header := map[string][]string{}
    50  	statusCode := http.StatusInternalServerError
    51  	header[larkevent.ContentTypeHeader] = []string{larkevent.DefaultContentType}
    52  	eventResp := &larkevent.EventResp{
    53  		Header:     header,
    54  		Body:       []byte(fmt.Sprintf(larkevent.WebhookResponseFormat, "Server Internal Error")),
    55  		StatusCode: statusCode,
    56  	}
    57  	return eventResp
    58  }
    59  
    60  func (h *CardActionHandler) Handle(ctx context.Context, req *larkevent.EventReq) (eventResp *larkevent.EventResp) {
    61  	h.Config.Logger.Debug(ctx, fmt.Sprintf("card request: header:%v,body:%s", req.Header, string(req.Body)))
    62  	defer func() {
    63  		if err := recover(); err != nil {
    64  			h.Config.Logger.Error(ctx, fmt.Sprintf("handle cardAction,path:%s, error:%v", req.RequestURI, err))
    65  			eventResp = recoveryResult()
    66  		}
    67  	}()
    68  	cardAction := &CardAction{}
    69  	err := json.Unmarshal(req.Body, cardAction)
    70  	if err != nil {
    71  		return processError(ctx, h.Config.Logger, req.RequestURI, err)
    72  	}
    73  	cardAction.EventReq = req
    74  
    75  	if larkevent.ReqType(cardAction.Type) != larkevent.ReqTypeChallenge {
    76  		err = h.VerifySign(ctx, req)
    77  		if err != nil {
    78  			return processError(ctx, h.Config.Logger, req.RequestURI, err)
    79  		}
    80  	}
    81  
    82  	result, err := h.DoHandle(ctx, cardAction)
    83  	if err != nil {
    84  		return processError(ctx, h.Config.Logger, req.RequestURI, err)
    85  	}
    86  	return result
    87  }
    88  
    89  func (h *CardActionHandler) Logger() larkcore.Logger {
    90  	return h.Config.Logger
    91  }
    92  
    93  func (h *CardActionHandler) InitConfig(options ...larkevent.OptionFunc) {
    94  	for _, option := range options {
    95  		option(h.Config)
    96  	}
    97  	larkcore.NewLogger(h.Config)
    98  }
    99  
   100  func NewCardActionHandler(verificationToken, eventEncryptKey string, handler func(context.Context, *CardAction) (interface{}, error)) *CardActionHandler {
   101  	h := &CardActionHandler{
   102  		verificationToken: verificationToken,
   103  		eventEncryptKey:   eventEncryptKey,
   104  		handler:           handler,
   105  		Config:            &larkcore.Config{Logger: larkcore.NewEventLogger()},
   106  	}
   107  	return h
   108  }
   109  
   110  func (h *CardActionHandler) Event() interface{} {
   111  	return &CardAction{}
   112  }
   113  
   114  var notFoundCardHandlerErr = errors.New("card action handler not found")
   115  
   116  func (h *CardActionHandler) AuthByChallenge(ctx context.Context, cardAction *CardAction) (*larkevent.EventResp, error) {
   117  	header := map[string][]string{}
   118  	header[larkevent.ContentTypeHeader] = []string{larkevent.DefaultContentType}
   119  	hookType := larkevent.ReqType(cardAction.Type)
   120  	challenge := cardAction.Challenge
   121  	if hookType == larkevent.ReqTypeChallenge {
   122  		if h.verificationToken != cardAction.Token {
   123  			err := errors.New("the result of auth by challenge failed")
   124  			return nil, err
   125  		}
   126  		eventResp := larkevent.EventResp{
   127  			Header:     header,
   128  			Body:       []byte(fmt.Sprintf(larkevent.ChallengeResponseFormat, challenge)),
   129  			StatusCode: http.StatusOK,
   130  		}
   131  		h.Config.Logger.Info(ctx, fmt.Sprintf("AuthByChallenge Success"))
   132  		return &eventResp, nil
   133  	}
   134  	return nil, nil
   135  }
   136  func (h *CardActionHandler) DoHandle(ctx context.Context, cardAction *CardAction) (*larkevent.EventResp, error) {
   137  	var err error
   138  	// auth by challenge
   139  	resp, err := h.AuthByChallenge(ctx, cardAction)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	if resp != nil {
   144  		return resp, nil
   145  	}
   146  
   147  	// 校验行为执行器
   148  	handler := h.handler
   149  	if handler == nil {
   150  		err = notFoundCardHandlerErr
   151  		return nil, err
   152  	}
   153  
   154  	// 执行事件行为处理器
   155  	result, err := handler(ctx, cardAction)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	header := map[string][]string{}
   161  	header[larkevent.ContentTypeHeader] = []string{larkevent.DefaultContentType}
   162  	if result == nil {
   163  		eventResp := &larkevent.EventResp{
   164  			Header:     header,
   165  			Body:       []byte(fmt.Sprintf(larkevent.WebhookResponseFormat, "success")),
   166  			StatusCode: http.StatusOK,
   167  		}
   168  		return eventResp, nil
   169  	}
   170  
   171  	var respBody []byte
   172  	switch r := result.(type) {
   173  	case string:
   174  		respBody = []byte(r)
   175  	case *CustomResp:
   176  		statusCode := r.StatusCode
   177  		if statusCode == 0 {
   178  			statusCode = http.StatusOK
   179  		}
   180  
   181  		b, err := json.Marshal(r.Body)
   182  		if err != nil {
   183  			return nil, err
   184  		}
   185  
   186  		eventResp := &larkevent.EventResp{
   187  			Header:     header,
   188  			Body:       b,
   189  			StatusCode: statusCode,
   190  		}
   191  		return eventResp, nil
   192  	default:
   193  		respBody, err = json.Marshal(result)
   194  	}
   195  
   196  	eventResp := &larkevent.EventResp{
   197  		Header:     header,
   198  		Body:       respBody,
   199  		StatusCode: http.StatusOK,
   200  	}
   201  
   202  	return eventResp, err
   203  }
   204  
   205  func (h *CardActionHandler) VerifySign(ctx context.Context, req *larkevent.EventReq) error {
   206  	if h.verificationToken == "" {
   207  		return nil
   208  	}
   209  
   210  	// 解析签名头
   211  	requestTimestamps := req.Header[larkevent.EventRequestTimestamp]
   212  	requestNonces := req.Header[larkevent.EventRequestNonce]
   213  
   214  	var requestTimestamp = ""
   215  	var requestNonce = ""
   216  	if len(requestTimestamps) > 0 {
   217  		requestTimestamp = requestTimestamps[0]
   218  	}
   219  	if len(requestNonces) > 0 {
   220  		requestNonce = requestNonces[0]
   221  	}
   222  
   223  	// 执行sha1签名计算
   224  	targetSign := Signature(requestTimestamp, requestNonce,
   225  		h.verificationToken,
   226  		string(req.Body))
   227  
   228  	sourceSigns := req.Header[larkevent.EventSignature]
   229  	var sourceSign = ""
   230  	if len(sourceSigns) > 0 {
   231  		sourceSign = sourceSigns[0]
   232  	}
   233  
   234  	// 验签
   235  	if targetSign == sourceSign {
   236  		return nil
   237  	}
   238  	return errors.New("the result of signature verification failed")
   239  }
   240  
   241  func Signature(timestamp, nonce, token, body string) string {
   242  	var b strings.Builder
   243  	b.WriteString(timestamp)
   244  	b.WriteString(nonce)
   245  	b.WriteString(token)
   246  	b.WriteString(body)
   247  	bs := []byte(b.String())
   248  	h := sha1.New()
   249  	_, _ = h.Write(bs)
   250  	bs = h.Sum(nil)
   251  	return fmt.Sprintf("%x", bs)
   252  }