tcibot/tciapi/tciapi.go

613 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}