Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
connection.go 12.65 KiB
package main

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"net/url"

	"github.com/gin-gonic/gin"
	"gitlab.eclipse.org/eclipse/xfsc/common-services/didcomm-connector/didcomm"
	"gitlab.eclipse.org/eclipse/xfsc/common-services/didcomm-connector/internal/config"
	"gitlab.eclipse.org/eclipse/xfsc/common-services/didcomm-connector/mediator"
	"gitlab.eclipse.org/eclipse/xfsc/common-services/didcomm-connector/mediator/database"
	"gitlab.eclipse.org/eclipse/xfsc/common-services/didcomm-connector/protocol"
)

type ConnectionResponse struct {
	database.Mediatee
	DidDoc *mediator.DidDocumentJSON `json:"didDocument,omitempty"`
}

// @Summary	Get connections
// @Schemes
// @Description	Returns a list with the existing connections
// @Tags			Connections
// @Accept			json
// @Produce		json
// @Success		200	{array}	database.Mediatee
// @Failure		500	"Internal Server Error"
// @Router			/admin/connections [get]
func (app *application) GetConnections(context *gin.Context) {
	logTag := "/admin/connections [get]"
	config.Logger.Info(logTag, "Start", true)

	var g *string
	group, ok := context.GetQuery("group")

	if ok {
		g = &group
	}

	connections, err := app.mediator.Database.GetMediatees(g)
	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusInternalServerError)
	}

	var responseObjects = make([]ConnectionResponse, 0)

	for _, x := range connections {

		doc, _ := app.mediator.DidResolver.ResolveDidAsJson(x.RemoteDid)

		response := ConnectionResponse{
			Mediatee: x,
			DidDoc:   doc,
		}

		responseObjects = append(responseObjects, response)
	}

	config.Logger.Info(logTag, "End", true)

	context.JSON(http.StatusOK, responseObjects)

}

// @Summary	Get connection
// @Schemes
// @Description	Returns a connection
// @Tags			Connections
// @Accept			json
// @Produce		json
// @Param			did	path		string	true	"DID"
// @Success		200	{object}	database.Mediatee
// @Failure		204	"No object found"
// @Failure		500	"Internal Server Error"
// @Router			/admin/connections/:did [get]
func (app *application) GetConnection(context *gin.Context) {
	logTag := "/admin/connections/{did} [get]"
	did := context.Param("did")
	config.Logger.Info(logTag, "did", did, "Start", true)
	connection, err := app.mediator.Database.GetMediatee(did)
	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusInternalServerError)
		return
	}

	doc, _ := app.mediator.DidResolver.ResolveDidAsJson(did)

	response := ConnectionResponse{
		Mediatee: *connection,
		DidDoc:   doc,
	}
	config.Logger.Info(logTag, "End", true)

	context.IndentedJSON(http.StatusOK, response)
}

// @Summary	Create a new connection
// @Schemes
// @Description	Creates a new connection
// @Tags			Connections
// @Accept			json
// @Produce		json
// @Param			connection	body		database.MediateeBase	true	"Connection"
// @Success		201			{object}	string
// @Failure		400			"Bad Request"
// @Failure		423			"Locked"
// @Failure		500			"Internal Server Error"
// @Router			/admin/connections [post]
/*func (app *application) CreateConnection(context *gin.Context) {
	logTag := "/admin/connections [post]"
	config.Logger.Info(logTag, "Start", true)
	context.Header("Content-Type", "application/json")

	var mediateeBase database.MediateeBase
	err := context.ShouldBindJSON(&mediateeBase)

	if err != nil {
		_ = app.SendPr(context, protocol.PR_INVALID_REQUEST, err)
		context.Status(http.StatusBadRequest)
		return
	}

	isBlocked, err := app.mediator.Database.IsBlocked(mediateeBase.RemoteDid)
	if err != nil {
		_ = app.SendPr(context, protocol.PR_INTERNAL_SERVER_ERROR, err)
		context.Status(http.StatusInternalServerError)
		return
	}
	if isBlocked {
		_ = app.SendPr(context, protocol.PR_DID_BLOCKED, err)
		context.Status(http.StatusLocked)
		return
	}
	err = app.mediator.ConnectionManager.StoreConnection(mediateeBase.Protocol, mediateeBase.RemoteDid, mediateeBase.Topic, mediateeBase.Properties)
	if err != nil {
		if err == connectionmanager.ERROR_PROTOCOL_NOT_SUPPORTED {
			_ = app.SendPr(context, protocol.PR_PROTOCOL_NOT_SUPPORTED, err)
			context.Status(http.StatusBadRequest)
		} else if err == connectionmanager.ERROR_INTERNAL {
			_ = app.SendPr(context, protocol.PR_INTERNAL_SERVER_ERROR, err)
			context.Status(http.StatusInternalServerError)
		} else if err == connectionmanager.ERROR_CONNECTION_ALREADY_EXISTS {
			_ = app.SendPr(context, protocol.PR_ALREADY_CONNECTED, err)
			context.Status(http.StatusInternalServerError)
		} else {
			_ = app.SendPr(context, protocol.PR_INTERNAL_SERVER_ERROR, err)
			context.Status(http.StatusInternalServerError)
		}
		return

	}
	/*oob := protocol.NewOutOfBand(app.mediator)
	invitation, err := oob.Handle(config.CurrentConfiguration.Label)
	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusInternalServerError)
		return
	}
	config.Logger.Info(logTag, "End", true)
	context.String(http.StatusCreated, invitation)
}*/

// @Summary	Deletes a connection
// @Schemes
// @Description	Deletes a connection
// @Tags			Connections
// @Accept			json
// @Produce		json
// @Param			did	path string	true	"DID"
// @Success		200			"OK"
// @Failure		500			"Internal Server Error"
// @Router			/admin/connections/{did} [delete]
func (app *application) DeleteConnection(context *gin.Context) {
	logTag := "/admin/connections/{did} [delete]"
	did := context.Param("did")
	config.Logger.Info(logTag, "did", did, "Start", true)
	err := app.mediator.Database.DeleteMediatee(did)
	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusInternalServerError)
		return
	}
	config.Logger.Info(logTag, "End", true)
	context.Status(http.StatusOK)

}

// @Summary	Updates a connection
// @Schemes
// @Description	Updates a connection
// @Tags			Connections
// @Accept			json
// @Produce		json
// @Param			did	path string	true	"DID"
// @Success		200			"OK"
// @Failure		500			"Internal Server Error"
// @Router			/admin/connections/{did} [delete]
func (app *application) UpdateConnection(context *gin.Context) {
	logTag := "/admin/connections/{did} [update]"
	did := context.Param("did")
	config.Logger.Info(logTag, "did", did, "Start", true)

	body, err := io.ReadAll(context.Request.Body)

	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusBadRequest)
		return
	}

	err = context.Request.Body.Close()

	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusBadRequest)
		return
	}

	if len(body) == 0 {
		config.Logger.Error(logTag, "Error", errors.New("no body"))
		context.Status(http.StatusBadRequest)
		return
	}

	var mediatee database.Mediatee

	err = json.Unmarshal(body, &mediatee)

	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusBadRequest)
		return
	}

	if mediatee.RemoteDid != "" && mediatee.RemoteDid != did {
		config.Logger.Error(logTag, "Error", errors.New("did mismatch"))
		context.Status(http.StatusBadRequest)
		return
	}

	mediatee.RemoteDid = did

	err = app.mediator.Database.UpdateMediatee(mediatee)
	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusInternalServerError)
		return
	}
	config.Logger.Info(logTag, "End", true)
	context.Status(http.StatusOK)

}

// @Summary	Blocks connection
// @Schemes
// @Description	Blocks connection
// @Tags			Connections
// @Accept			json
// @Produce		json
// @Param			did	path	string	true	"Did to be blocked"
// @Success		200	"OK"
// @Failure		500	"Internal Server Error"
// @Router			/admin/connections/block/{did} [post]
func (app *application) BlockConnection(context *gin.Context) {
	logTag := "/admin/connections/block/{did} [post]"
	did := context.Param("did")
	config.Logger.Info(logTag, "did", did, "Start", true)

	err := app.mediator.Database.BlockMediatee(did)
	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusInternalServerError)
		return
	}
	config.Logger.Info(logTag, "End", true)
	context.Status(http.StatusOK)
}

// @Summary	Unblock existing connection
// @Schemes
// @Description	Blocks existing connection
// @Tags			Connections
// @Accept			json
// @Produce		json
// @Param			did	path	string	true	"Did to be unblocked"
// @Success		200	"OK"
// @Failure		500	"Internal Server Error"
// @Router			/admin/connections/unblock/{did} [post]
func (app *application) UnblockConnection(context *gin.Context) {
	logTag := "/admin/connections/unblock/{did} [post]"
	did := context.Param("did")
	config.Logger.Info(logTag, "did", did, "Start", true)
	err := app.mediator.Database.UnblockMediatee(did)
	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusInternalServerError)
		return
	}
	config.Logger.Info(logTag, "End", true)
	context.Status(http.StatusOK)
}

// @Summary	Checks if a remoteDid is blocked
// @Schemes
// @Description	Checks if a remoteDid is blocked
// @Tags			Connections
// @Accept			json
// @Produce		json
// @Param			did	path		string	true	"Did"
// @Success		200	{object}	boolean
// @Failure		500	"Internal Server Error"
// @Router			/admin/connections/isblocked/{did} [get]
func (app *application) IsBlocked(context *gin.Context) {
	logTag := "/admin/connections/isblocked/{did} [get]"
	did := context.Param("did")

	config.Logger.Info(logTag, "did", did, "Start", true)
	ib, err := app.mediator.Database.IsBlocked(did)
	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusInternalServerError)
		return
	}
	config.Logger.Info(logTag, "End", true)
	context.IndentedJSON(http.StatusOK, ib)
}

// @Summary	Accept connection
// @Schemes
// @Description	accept connection
// @Tags			Connections
// @Accept			json
// @Produce		json
// @Success		200	"OK"
// @Failure		500	"Internal Server Error"
// @Router			/admin/connections/unblock/{did} [post]
func (app *application) AcceptConnection(context *gin.Context) {
	logTag := "/admin/connections/accept"
	config.Logger.Info(logTag, "Start", true)

	type Invitation struct {
		Invitation string `json:"invitation"`
		database.MediateeBase
	}

	var inv Invitation

	err := context.ShouldBindJSON(&inv)

	if inv.Invitation == "" ||
		inv.EventType == "" ||
		inv.Properties == nil ||
		inv.Topic == "" ||
		inv.Group == "" {
		_ = app.SendPr(context, protocol.PR_INVALID_REQUEST, err)
		context.Status(http.StatusBadRequest)
		return
	}

	if err != nil {
		_ = app.SendPr(context, protocol.PR_INVALID_REQUEST, err)
		context.Status(http.StatusBadRequest)
		return
	}

	uri, err := url.Parse(inv.Invitation)

	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusBadRequest)
		return
	}

	q, err := url.ParseQuery(uri.RawQuery)
	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusInternalServerError)
		return
	}

	oob := q.Get("_oob")

	b, err := base64.RawURLEncoding.DecodeString(oob)

	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusBadRequest)
		return
	}

	msg, err := app.mediator.UnpackMessage(string(b))

	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusBadRequest)
		return
	}

	var bodyJson map[string]interface{}

	err = json.Unmarshal([]byte(msg.Body), &bodyJson)

	if err != nil {
		config.Logger.Error("error unmarshalling", err)
		context.Status(http.StatusInternalServerError)
		return
	}

	s, err := mediator.CreateServiceEntry()

	if err != nil {
		config.Logger.Error("cant create Service", err)
		context.Status(http.StatusInternalServerError)
		return
	}

	peerDid, err := mediator.NumAlgo2([]didcomm.Service{s}, app.mediator.SecretsResolver, app.mediator.DidResolver)

	if err != nil {
		config.Logger.Error("cant create Service", err)
		context.Status(http.StatusInternalServerError)
		return
	}

	// routing key must be extracted from mediaton request

	peerdid, err := app.mediator.ConnectionManager.Connect(uri.Scheme+"://"+uri.Host+"/message/receive", *msg.From, peerDid, bodyJson["auth"].(string))

	if err != nil {
		config.Logger.Error("cant create Service", err)
		context.Status(http.StatusInternalServerError)
		return
	}

	err = app.mediator.ConnectionManager.StoreConnection(inv.Protocol, *msg.From, inv.Topic, inv.Properties, inv.EventType, []string{peerdid}, inv.Group)

	if err != nil {
		config.Logger.Error(logTag, "Error", err)
		context.Status(http.StatusBadRequest)
		return
	}
	config.Logger.Info(logTag, "End", true)
	context.JSON(200, peerDid)
}

func (app *application) SendPr(context *gin.Context, pr didcomm.Message, err error) error {
	context.Header("Content-Type", "application/json")
	if err != nil {
		config.Logger.Error("Problem Report", "err", err)
	}
	packMsg, err := app.mediator.PackPlainMessage(pr)
	if err != nil {
		config.Logger.Error("Problem Report", "err", err)
		context.Status(http.StatusBadRequest)
		return err
	}
	context.String(http.StatusBadRequest, packMsg)
	return nil
}