tcibot/tciapi/tciapi.go

613 lines
15 KiB
Go
Raw Normal View History

2023-04-10 13:06:15 +03:00
package tciapi
import(
"io"
"os"
"fmt"
"bytes"
"net/url"
"net/http"
"crypto/tls"
"crypto/x509"
"encoding/json"
)
const apiUrlAuth = "auth"
const apiUrlOrder = "order"
const apiUrlCode = "proxy/csr"
const apiUrlValidate = "order/check-domain-ownership"
type stUserAuth struct {
Status string `json:"status"`
StatusCode int `json:"statusCode"`
Message string `json:"message"`
Result stUserAuthResult `json:"result"`
}
type stUserAuthResult struct {
AccessToken string `json:"access_token"`
}
type stUnauthorized struct {
StatusCode int `json:"statusCode"`
ErrorDesc string `json:"error"`
Message string `json:"message"`
}
type stErrorResp struct {
StatusCode int `json:"statusCode"`
ErrorDesc string `json:"error"`
Message []string `json:"message"`
}
type stErrorLimit struct {
StatusCode int `json:"statusCode"`
ErrorDesc string `json:"error"`
Message string `json:"message"`
}
type stCode struct {
Status string `json:"status"`
StatusCode int `json:"statusCode"`
Message string `json:"message"`
Result stCodeResult `json:"result"`
}
type stCodeResult struct {
Code string `json:"code"`
DocumentName string `json:"filename"`
}
type stOrderResponse struct {
Status string `json:"status"`
StatusCode int `json:"statusCode"`
Message string `json:"message"`
Result stOrderResult `json:"result"`
}
type stOrderResult struct {
DomainName string `json:"domain"`
ValidityDays int `json:"period"`
Method string `json:"method"`
Wildcard bool `json:"wildcard"`
CSR string `json:"csr"`
Cryptosystem string `json:"certificate_type"`
Type string `json:"certificate_kind"`
DCVMethod string `json:"confirmation_type"`
CustomerId string `json:"customerId"`
Issued string `json:"issued"`
LastCodeDate string `json:"last_new_code_date"`
Id string `json:"id"`
OrderNumber int `json:"number"`
Created string `json:"created"`
Updated string `json:"updated"`
State string `json:"state"`
AttemptsCounter int `json:"confirm_attempts"`
UserInfo stUserInfo `json:"user"`
CertificateInfo stCertificateInfo `json:"certificate"`
}
type stCertificateInfo struct {
Id string `json:"id"`
Created string `json:"created"`
Updated string `json:"updated"`
DomainName string `json:"domain"`
NotBeforeDate string `json:"start"`
NotAfterDate string `json:"finish"`
State string `json:"state"`
SerialNumber string `json:"serial"`
IssuerName string `json:"ca_name"`
Data string `json:"body"`
}
type stUserInfo struct {
Id string `json:"id"`
Created string `json:"created"`
Updated string `json:"updated"`
Number int `json:"number"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
MiddleName string `json:"middleName"`
UserName string `json:"username"`
Password string `json:"password"`
Agreement bool `json:"agreement"`
Convention bool `json:"convention"`
}
type rtOrder struct {
DomainName string `json:"domain"`
ValidityDays int `json:"period"`
OrderMethod string `json:"method"`
Wildcard bool `json:"wildcard"`
CSR string `json:"csr"`
Cryptosystem string `json:"certificate_type"`
Type string `json:"certificate_kind"`
DCVMethod string `json:"confirmation_type"`
}
type rtCode struct {
OrderId string `json:"order_id"`
UserId string `json:"user_id"`
Cryptosystem string `json:"type"`
DomainName string `json:"name"`
DCVMethod string `json:"check_type"`
CSR string `json:"csr"`
}
type rtValidate struct {
OrderId string `json:"id"`
}
type TciApi struct {
Connected bool // Состояние "подключения"; true - был успешный логин и есть активный токен (JWT).
Complete bool
Login string // Логин - имя пользователя на сервисе ЦС.
AuthToken string // Токен для аутентификации (получен с сервера на этапе "подключения").
Hostname string // Имя хоста сервиса ЦС.
APIPath string // Путь к API на сервере.
CurrentOrderId string // Используемый идентификатор заказа.
CurrentUserId string // Используемый идентификатор пользователя.
CurrentCSR string
CertData string
ValidationCode string // Код валидации.
ValidationURL string // Адрес документа для размещения кода валидации.
DebugMode bool
DomainName string // Используемое в заказе имя домена.
C *http.Client
}
func NewTciApi(hostname, api, login, passwd string, rootCert string, debugFlag bool) (instance *TciApi, status bool) {
var res TciApi
defer func() {
if boo := recover(); boo != nil {
instance = nil
status = false
return
}
}()
var tlsConfig tls.Config
res.DebugMode = debugFlag
if rootCert != "" {
caCertPool := x509.NewCertPool()
if !caCertPool.AppendCertsFromPEM([]byte(rootCert)) {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", "Unable to load root certificate!")
return &res, false
}
tlsConfig.RootCAs = caCertPool
}
res.C = &http.Client{ // TODO: добавить (здесь) параметры в конфигурацию клиента.
Transport: &http.Transport{
TLSClientConfig: &tlsConfig,
},
}
res.Login = login
res.Hostname = "https://" + hostname + "/"
res.APIPath = api
response, err := res.C.PostForm(res.Hostname + res.APIPath + apiUrlAuth,
url.Values{
"username" : { login },
"password" : { passwd },
})
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
return &res, false
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
return &res, false
}
switch response.StatusCode {
case 200: // 201
{
s := new(stUserAuth)
e := json.Unmarshal([]byte(body), s)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return &res, false
}
res.AuthToken = s.Result.AccessToken
if debugFlag {
fmt.Fprintf(os.Stderr, "Connected (%s)\n", res.Hostname)
fmt.Fprintf(os.Stderr, "%s\n", res.AuthToken)
}
return &res, true
}
case 401:
{
s := new(stUnauthorized)
e := json.Unmarshal([]byte(body), s)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return &res, false
}
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
return &res, false
}
default:
{
fmt.Fprintf(os.Stderr, "Unexpected response (%s)!\n", res.Hostname)
return &res, false
}
}
return &res, true
}
func (r *TciApi) RequestCertificate(name, csr string) (status bool){
defer func() {
if boo := recover(); boo != nil {
status = false
return
}
}()
s := new(rtOrder)
s.DomainName = name
s.Cryptosystem = "ecdsa"
s.CSR = csr
s.ValidityDays = 90
s.OrderMethod = "auto"
s.Type = "domain"
s.DCVMethod = "http"
s.Wildcard = false
reqBody, err := json.Marshal(s)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
return false
}
requestData := bytes.NewBuffer(reqBody)
req, err := http.NewRequest("POST", r.Hostname + r.APIPath + apiUrlOrder, requestData)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
return false
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer " + r.AuthToken)
req.Header.Set("Cookie", "access_token=" + r.AuthToken)
if r.DebugMode {
fmt.Fprintf(os.Stderr, "\nSending order (%s)\n", r.Hostname)
}
response, err := r.C.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
return false
}
defer response.Body.Close()
respBody, err := io.ReadAll(response.Body)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
return false
}
if r.DebugMode {
fmt.Fprintf(os.Stderr, "Response code: %d\n", response.StatusCode)
}
switch response.StatusCode {
case 201:
{
orderData := new(stOrderResponse)
e := json.Unmarshal([]byte(respBody), orderData)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return false
}
if r.DebugMode {
fmt.Fprintf(os.Stderr, "OrderId: %s\n\n", orderData.Result.Id)
}
r.DomainName = name
r.CurrentOrderId = orderData.Result.Id
r.CurrentUserId = orderData.Result.CustomerId
r.CurrentCSR = csr
return true
}
case 400:
{
s := new(stErrorResp)
e := json.Unmarshal([]byte(respBody), s)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return false
}
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
return false
}
case 401:
{
s := new(stUnauthorized)
e := json.Unmarshal([]byte(respBody), s)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return false
}
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
return false
}
case 429:
{
s := new(stErrorLimit)
e := json.Unmarshal([]byte(respBody), s)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return false
}
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
return false
}
default:
{
fmt.Fprintf(os.Stderr, "Unexpected response (%s)!\n", r.Hostname)
return false
}
}
return true
}
func (r *TciApi) RequestCode() (status bool){
defer func() {
if boo := recover(); boo != nil {
status = false
return
}
}()
if r.DebugMode {
fmt.Fprintf(os.Stderr, "Requesting code for DCV\n")
}
req, err := http.NewRequest("GET", r.Hostname + r.APIPath + apiUrlCode + "/" + r.CurrentOrderId, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
return false
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer " + r.AuthToken)
response, err := r.C.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
return false
}
defer response.Body.Close()
respBody, err := io.ReadAll(response.Body)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
return false
}
switch response.StatusCode {
case 200: //201
{
codeData := new(stCode)
e := json.Unmarshal([]byte(respBody), codeData)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return false
}
r.ValidationCode = codeData.Result.Code
r.ValidationURL = codeData.Result.DocumentName
if r.DebugMode {
fmt.Fprintf(os.Stderr, "Code: %s\nDocument: %s\n", codeData.Result.Code, codeData.Result.DocumentName)
}
return true
}
case 400:
{
s := new(stErrorResp)
e := json.Unmarshal([]byte(respBody), s)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return false
}
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
return false
}
case 401:
{
s := new(stUnauthorized)
e := json.Unmarshal([]byte(respBody), s)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return false
}
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
return false
}
default:
{
fmt.Fprintf(os.Stderr, "Unexpected response (%s)!\n", r.Hostname)
return false
}
}
return true
}
func (r *TciApi) StartDCV() (status bool){
defer func() {
if boo := recover(); boo != nil {
status = false
return
}
}()
s := new(rtValidate)
s.OrderId = r.CurrentOrderId
reqBody, err := json.Marshal(s)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
return false
}
requestData := bytes.NewBuffer(reqBody)
req, err := http.NewRequest("POST", r.Hostname + r.APIPath + apiUrlValidate, requestData)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
return false
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer " + r.AuthToken)
if r.DebugMode {
fmt.Fprintf(os.Stderr, "Start DCV\n")
}
response, err := r.C.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
return false
}
defer response.Body.Close()
respBody, err := io.ReadAll(response.Body)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
return false
}
fmt.Printf("Status Code: %d\n", response.StatusCode)
switch response.StatusCode {
case 200:
{
if r.DebugMode {
fmt.Fprintf(os.Stderr, "DCV process started\n")
}
return true
}
case 400:
{
s := new(stErrorResp)
e := json.Unmarshal([]byte(respBody), s)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return false
}
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
return false
}
case 401:
{
s := new(stUnauthorized)
e := json.Unmarshal([]byte(respBody), s)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return false
}
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
return false
}
default:
{
fmt.Fprintf(os.Stderr, "Unexpected response (%s)!\n", r.Hostname)
return false
}
}
return true
}
func (r *TciApi) RequestStatus() (res, status bool){
defer func() {
if boo := recover(); boo != nil {
res = false
status = false
return
}
}()
req, err := http.NewRequest("GET", r.Hostname + r.APIPath + apiUrlOrder, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
return false, false
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer " + r.AuthToken)
if r.DebugMode {
fmt.Fprintf(os.Stderr, "Status check\n")
}
response, err := r.C.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
return false, false
}
defer response.Body.Close()
respBody, err := io.ReadAll(response.Body)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
return false, false
}
switch response.StatusCode {
case 200:
{
s := new([]stOrderResult)
e := json.Unmarshal([]byte(respBody), s)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return false, false
}
found := false
var order stOrderResult
for _, o := range(*s) {
if r.CurrentOrderId == o.Id {
found = true
order = o
}
}
if found {
if (order.State == "validated") && (len(order.CertificateInfo.Data) > 0) {
r.CertData = order.CertificateInfo.Data
if r.DebugMode {
fmt.Fprintf(os.Stderr, "DCV confirmed!\n")
}
return true, true
}else{
return false, true
}
}else{
return false, false
}
}
case 400:
{
s := new(stErrorResp)
e := json.Unmarshal([]byte(respBody), s)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return false, false
}
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
return false, false
}
case 401:
{
s := new(stUnauthorized)
e := json.Unmarshal([]byte(respBody), s)
if e != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
return false, false
}
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
return false, false
}
default:
{
fmt.Fprintf(os.Stderr, "Unexpected response (%s)!\n", r.Hostname)
return false, false
}
}
return false, false
}