diff --git a/src/config/constants.go b/src/config/constants.go index d0910c4..f4eaa31 100644 --- a/src/config/constants.go +++ b/src/config/constants.go @@ -10,6 +10,16 @@ const ( // Application mode const ( - SingleTenat = "single-tenant" - MultiTenant = "multi-tenant" + SingleTenant = "single-tenant" + MultiTenant = "multi-tenant" +) + +// All http path url variables +const ( + OrganizationId = "organizationId" + DataAgreementId = "dataAgreementId" + DataAttributeId = "dataAttributeId" + WebhookId = "webhookId" + WebhookDeliveryId = "deliveryId" + PolicyId = "policyId" ) diff --git a/src/handler/actionlog_handler.go b/src/handlerv1/actionlog_handler.go similarity index 98% rename from src/handler/actionlog_handler.go rename to src/handlerv1/actionlog_handler.go index 74e8123..20c5488 100644 --- a/src/handler/actionlog_handler.go +++ b/src/handlerv1/actionlog_handler.go @@ -1,4 +1,4 @@ -package handler +package handlerv1 import ( "encoding/json" diff --git a/src/handler/consent_handler.go b/src/handlerv1/consent_handler.go similarity index 99% rename from src/handler/consent_handler.go rename to src/handlerv1/consent_handler.go index 0c73585..97101a6 100644 --- a/src/handler/consent_handler.go +++ b/src/handlerv1/consent_handler.go @@ -1,4 +1,4 @@ -package handler +package handlerv1 import ( "encoding/json" diff --git a/src/handler/consent_handler_helper.go b/src/handlerv1/consent_handler_helper.go similarity index 99% rename from src/handler/consent_handler_helper.go rename to src/handlerv1/consent_handler_helper.go index 38d25e7..9c6cc5f 100644 --- a/src/handler/consent_handler_helper.go +++ b/src/handlerv1/consent_handler_helper.go @@ -1,4 +1,4 @@ -package handler +package handlerv1 import ( "time" diff --git a/src/handler/consenthistory_handler.go b/src/handlerv1/consenthistory_handler.go similarity index 99% rename from src/handler/consenthistory_handler.go rename to src/handlerv1/consenthistory_handler.go index 7239c5d..0c0c90d 100644 --- a/src/handler/consenthistory_handler.go +++ b/src/handlerv1/consenthistory_handler.go @@ -1,4 +1,4 @@ -package handler +package handlerv1 import ( "encoding/json" diff --git a/src/handler/datarequest_handler.go b/src/handlerv1/datarequest_handler.go similarity index 99% rename from src/handler/datarequest_handler.go rename to src/handlerv1/datarequest_handler.go index e21eedd..0239c8b 100644 --- a/src/handler/datarequest_handler.go +++ b/src/handlerv1/datarequest_handler.go @@ -1,4 +1,4 @@ -package handler +package handlerv1 import ( "encoding/json" diff --git a/src/handler/iam_handler.go b/src/handlerv1/iam_handler.go similarity index 99% rename from src/handler/iam_handler.go rename to src/handlerv1/iam_handler.go index 4a0d1bf..f671295 100644 --- a/src/handler/iam_handler.go +++ b/src/handlerv1/iam_handler.go @@ -1,4 +1,4 @@ -package handler +package handlerv1 import ( "bytes" diff --git a/src/handler/mobile_handler.go b/src/handlerv1/mobile_handler.go similarity index 99% rename from src/handler/mobile_handler.go rename to src/handlerv1/mobile_handler.go index 3141114..2a86191 100644 --- a/src/handler/mobile_handler.go +++ b/src/handlerv1/mobile_handler.go @@ -1,4 +1,4 @@ -package handler +package handlerv1 import ( "encoding/json" diff --git a/src/handler/notification_handler.go b/src/handlerv1/notification_handler.go similarity index 92% rename from src/handler/notification_handler.go rename to src/handlerv1/notification_handler.go index 2f0355c..aca8fda 100644 --- a/src/handler/notification_handler.go +++ b/src/handlerv1/notification_handler.go @@ -1,4 +1,4 @@ -package handler +package handlerv1 import "github.com/bb-consent/api/src/org" diff --git a/src/handler/orgType_handler.go b/src/handlerv1/orgType_handler.go similarity index 99% rename from src/handler/orgType_handler.go rename to src/handlerv1/orgType_handler.go index 0d84285..ceab1bd 100644 --- a/src/handler/orgType_handler.go +++ b/src/handlerv1/orgType_handler.go @@ -1,4 +1,4 @@ -package handler +package handlerv1 import ( "bytes" diff --git a/src/handler/organization_handler.go b/src/handlerv1/organization_handler.go similarity index 99% rename from src/handler/organization_handler.go rename to src/handlerv1/organization_handler.go index 61de00d..22911e5 100644 --- a/src/handler/organization_handler.go +++ b/src/handlerv1/organization_handler.go @@ -1,4 +1,4 @@ -package handler +package handlerv1 import ( "bytes" diff --git a/src/handler/user_handler.go b/src/handlerv1/user_handler.go similarity index 99% rename from src/handler/user_handler.go rename to src/handlerv1/user_handler.go index d4712cc..e5eeb38 100644 --- a/src/handler/user_handler.go +++ b/src/handlerv1/user_handler.go @@ -1,4 +1,4 @@ -package handler +package handlerv1 import ( "encoding/json" diff --git a/src/handler/webhooks_handler.go b/src/handlerv1/webhooks_handler.go similarity index 99% rename from src/handler/webhooks_handler.go rename to src/handlerv1/webhooks_handler.go index ef7d938..203737b 100644 --- a/src/handler/webhooks_handler.go +++ b/src/handlerv1/webhooks_handler.go @@ -1,4 +1,4 @@ -package handler +package handlerv1 import ( "encoding/json" diff --git a/src/handlerv2/adddataagreement_handler.go b/src/handlerv2/adddataagreement_handler.go new file mode 100644 index 0000000..0f41c3a --- /dev/null +++ b/src/handlerv2/adddataagreement_handler.go @@ -0,0 +1,126 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + + "github.com/asaskevich/govalidator" + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type purpose struct { + Name string `valid:"required"` + Description string `valid:"required"` + LawfulBasisOfProcessing int + PolicyURL string `valid:"required"` + AttributeType int + Jurisdiction string + Disclosure string + IndustryScope string + DataRetention org.DataRetention + Restriction string + Shared3PP bool + SSIID string +} + +type addPurposeReq struct { + purpose +} + +// Check if the lawful usage ID provided is valid +func isValidLawfulBasisOfProcessing(lawfulBasis int) bool { + isFound := false + for _, lawfulBasisOfProcessingMapping := range org.LawfulBasisOfProcessingMappings { + if lawfulBasisOfProcessingMapping.ID == lawfulBasis { + isFound = true + break + } + } + + return isFound +} + +// Fetch the lawful usage based on the lawful basis ID +func getLawfulUsageByLawfulBasis(lawfulBasis int) bool { + if lawfulBasis == org.ConsentBasis { + return false + } else { + return true + } +} + +// AddDataAgreement Adds a single data agreement to the organization +func AddDataAgreement(w http.ResponseWriter, r *http.Request) { + organizationID := r.Header.Get(config.OrganizationId) + + o, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to fetch organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + var pReq addPurposeReq + b, _ := ioutil.ReadAll(r.Body) + defer r.Body.Close() + + json.Unmarshal(b, &pReq) + + // validating request payload + valid, err := govalidator.ValidateStruct(pReq) + if !valid { + log.Printf("Missing mandatory fields for a adding consent purpose to org") + common.HandleError(w, http.StatusBadRequest, err.Error(), err) + return + } + + // Proceed if lawful basis of processing provided is valid + if !isValidLawfulBasisOfProcessing(pReq.LawfulBasisOfProcessing) { + m := fmt.Sprintf("Invalid lawful basis of processing provided") + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + tempLawfulUsage := getLawfulUsageByLawfulBasis(pReq.LawfulBasisOfProcessing) + + newPurpose := org.Purpose{ + ID: primitive.NewObjectID().Hex(), + Name: pReq.Name, + Description: pReq.Description, + LawfulUsage: tempLawfulUsage, + LawfulBasisOfProcessing: pReq.LawfulBasisOfProcessing, + PolicyURL: pReq.PolicyURL, + AttributeType: pReq.AttributeType, + Jurisdiction: pReq.Jurisdiction, + Disclosure: pReq.Disclosure, + IndustryScope: pReq.IndustryScope, + DataRetention: pReq.DataRetention, + Restriction: pReq.Restriction, + Shared3PP: pReq.Shared3PP, + SSIID: pReq.SSIID, + } + o.Purposes = append(o.Purposes, newPurpose) + + _, err = org.UpdatePurposes(o.ID.Hex(), o.Purposes) + if err != nil { + m := fmt.Sprintf("Failed to update purpose to organization: %v", o.Name) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + /* + u, err := user.Get(token.GetUserID(r)) + if err != nil { + //notifications.SendPurposeUpdateNotification(u, o.ID.Hex(), ) + } + */ + response, _ := json.Marshal(newPurpose) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + w.Write(response) +} diff --git a/src/handlerv2/adddataattribute_handler.go b/src/handlerv2/adddataattribute_handler.go new file mode 100644 index 0000000..013031b --- /dev/null +++ b/src/handlerv2/adddataattribute_handler.go @@ -0,0 +1,81 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + + "github.com/asaskevich/govalidator" + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type template struct { + Consent string `valid:"required"` + PurposeIDs []string `valid:"required"` +} +type templateReq struct { + Templates []template +} + +// AddDataAttribute Adds an organization data attribute +func AddDataAttribute(w http.ResponseWriter, r *http.Request) { + organizationID := r.Header.Get(config.OrganizationId) + + o, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to fetch organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + var tReq templateReq + b, _ := ioutil.ReadAll(r.Body) + defer r.Body.Close() + + json.Unmarshal(b, &tReq) + + // validating request payload + valid, err := govalidator.ValidateStruct(tReq) + if !valid { + log.Printf("Missing mandatory fields for adding consent template to org: %v", organizationID) + common.HandleError(w, http.StatusBadRequest, err.Error(), err) + return + } + + // validating purposeIDs provided + for _, t := range tReq.Templates { + // checking if purposeID provided exist in the org + for _, p := range t.PurposeIDs { + _, err = org.GetPurpose(organizationID, p) + if err != nil { + m := fmt.Sprintf("Invalid purposeID:%v provided;Failed to update templates to organization: %v", p, o.Name) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + } + + // Appending the new template to existing org templates + o.Templates = append(o.Templates, org.Template{ + ID: primitive.NewObjectID().Hex(), + Consent: t.Consent, + PurposeIDs: t.PurposeIDs, + }) + } + + orgResp, err := org.UpdateTemplates(o.ID.Hex(), o.Templates) + if err != nil { + m := fmt.Sprintf("Failed to update templates to organization: %v", o.Name) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + response, _ := json.Marshal(organization{orgResp}) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusCreated) + w.Write(response) +} diff --git a/src/handlerv2/addidentityprovider_handler.go b/src/handlerv2/addidentityprovider_handler.go new file mode 100644 index 0000000..98cff62 --- /dev/null +++ b/src/handlerv2/addidentityprovider_handler.go @@ -0,0 +1,405 @@ +package handlerv2 + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "strings" + "time" + + "github.com/asaskevich/govalidator" + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" +) + +var timeout time.Duration + +var iamConfig config.Iam +var twilioConfig config.Twilio + +// IamInit Initialize the IAM handler +func IamInit(config *config.Configuration) { + iamConfig = config.Iam + twilioConfig = config.Twilio + timeout = time.Duration(time.Duration(iamConfig.Timeout) * time.Second) + + /* + memStorage := storage.NewMemoryStorage() + s := scheduler.New(memStorage) + _, err := s.RunEvery(24*time.Hour, clearStaleOtps) + if err != nil { + log.Printf("err in scheduling clearStaleOtps: %v", err) + } + + //TODO: Enable this later phase + //s.Start() + */ +} + +// IdentityProviderReq Describes the request payload to create and update an external identity provider +type IdentityProviderReq struct { + AuthorizationURL string `json:"authorizationUrl" valid:"required"` + TokenURL string `json:"tokenUrl" valid:"required"` + LogoutURL string `json:"logoutUrl"` + ClientID string `json:"clientId" valid:"required"` + ClientSecret string `json:"clientSecret" valid:"required"` + JWKSURL string `json:"jwksUrl"` + UserInfoURL string `json:"userInfoUrl"` + ValidateSignature bool `json:"validateSignature"` + DisableUserInfo bool `json:"disableUserInfo"` + Issuer string `json:"issuer"` + DefaultScope string `json:"defaultScope"` +} + +type iamToken struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + RefreshExpiresIn int `json:"refresh_expires_in"` + RefreshToken string `json:"refresh_token"` + TokenType string `json:"token_type"` +} + +type iamError struct { + ErrorType string `json:"error"` + Error string `json:"error_description"` +} + +func getAdminToken() (iamToken, int, iamError, error) { + t, status, iamErr, err := getToken(iamConfig.AdminUser, iamConfig.AdminPassword, "admin-cli", "master") + return t, status, iamErr, err +} + +func getToken(username string, password string, clientID string, realm string) (iamToken, int, iamError, error) { + var tok iamToken + var e iamError + var status = http.StatusInternalServerError + + data := url.Values{} + data.Set("username", username) + data.Add("password", password) + data.Add("client_id", clientID) + data.Add("grant_type", "password") + + resp, err := http.PostForm(iamConfig.URL+"/realms/"+realm+"/protocol/openid-connect/token", data) + if err != nil { + return tok, status, e, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + + if err != nil { + return tok, status, e, err + } + if resp.StatusCode != http.StatusOK { + var e iamError + json.Unmarshal(body, &e) + return tok, resp.StatusCode, e, errors.New("failed to get token") + } + json.Unmarshal(body, &tok) + + return tok, resp.StatusCode, e, err +} + +// Helper function to add identity provider to iGrant.io IAM +func addIdentityProvider(identityProviderRepresentation org.IdentityProviderRepresentation, adminToken string) (int, iamError, error) { + var e iamError + var status = http.StatusInternalServerError + jsonReq, _ := json.Marshal(identityProviderRepresentation) + req, err := http.NewRequest("POST", iamConfig.URL+"/admin/realms/"+iamConfig.Realm+"/identity-provider/instances", bytes.NewBuffer(jsonReq)) + if err != nil { + return status, e, err + } + + req.Header.Add("Authorization", "Bearer "+adminToken) + req.Header.Add(config.ContentTypeHeader, config.ContentTypeJSON) + + client := http.Client{ + Timeout: timeout, + } + resp, err := client.Do(req) + if err != nil { + return status, e, err + } + + if resp.StatusCode != http.StatusCreated { + body, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + + type errorMsg struct { + ErrorMessage string `json:"errorMessage"` + } + var errMsg errorMsg + json.Unmarshal(body, &errMsg) + e.Error = errMsg.ErrorMessage + e.ErrorType = "Identity provider creation failed" + return resp.StatusCode, e, errors.New("Failed to create identity provider") + } + + defer resp.Body.Close() + + return resp.StatusCode, e, err +} + +// Helper function to add OpenID client to manage login sessions for the external identity provider +func addOpenIDClient(keycloakOpenIDClient org.KeycloakOpenIDClient, adminToken string) (int, iamError, error) { + + var e iamError + var status = http.StatusInternalServerError + jsonReq, _ := json.Marshal(keycloakOpenIDClient) + req, err := http.NewRequest("POST", iamConfig.URL+"/admin/realms/"+iamConfig.Realm+"/clients", bytes.NewBuffer(jsonReq)) + if err != nil { + return status, e, err + } + + req.Header.Add("Authorization", "Bearer "+adminToken) + req.Header.Add(config.ContentTypeHeader, config.ContentTypeJSON) + + client := http.Client{ + Timeout: timeout, + } + resp, err := client.Do(req) + if err != nil { + return status, e, err + } + + if resp.StatusCode != http.StatusCreated { + body, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + + type errorMsg struct { + ErrorMessage string `json:"errorMessage"` + } + var errMsg errorMsg + json.Unmarshal(body, &errMsg) + e.Error = errMsg.ErrorMessage + e.ErrorType = "OpenID client creation failed" + return resp.StatusCode, e, errors.New("Failed to create OpenID client") + } + + body, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + var responseBody interface{} + json.Unmarshal(body, &responseBody) + + return resp.StatusCode, e, err + +} + +// AddIdentityProvider Add an external identity provider for an organization +func AddIdentityProvider(w http.ResponseWriter, r *http.Request) { + + // Note: Set OpenID-Connect as subscription method for the organization + // Execute set subscription method API to do the same. + + // Get the org ID and fetch the organization from the db. + organizationID := r.Header.Get(config.OrganizationId) + o, err := org.Get(organizationID) + + if err != nil { + m := fmt.Sprintf("Failed to fetch org; Failed to create identity provider for %v", organizationID) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + if o.ExternalIdentityProviderAvailable { + m := fmt.Sprintf("External IDP exists; Try to update instead of create; Failed to create identity provider for %v", organizationID) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + // Deserializing the request payload to struct + b, _ := ioutil.ReadAll(r.Body) + defer r.Body.Close() + + var addIdentityProviderReq IdentityProviderReq + json.Unmarshal(b, &addIdentityProviderReq) + + // validating request payload + valid, err := govalidator.ValidateStruct(addIdentityProviderReq) + if valid != true { + m := fmt.Sprintf("Missing mandatory params for creating identity provider for org:%v\n", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + var identityProviderOpenIDConfig org.IdentityProviderOpenIDConfig + + // OpenID config + identityProviderOpenIDConfig.AuthorizationURL = addIdentityProviderReq.AuthorizationURL + identityProviderOpenIDConfig.TokenURL = addIdentityProviderReq.TokenURL + identityProviderOpenIDConfig.LogoutURL = addIdentityProviderReq.LogoutURL + identityProviderOpenIDConfig.ClientID = addIdentityProviderReq.ClientID + identityProviderOpenIDConfig.ClientSecret = addIdentityProviderReq.ClientSecret + identityProviderOpenIDConfig.JWKSURL = addIdentityProviderReq.JWKSURL + identityProviderOpenIDConfig.UserInfoURL = addIdentityProviderReq.UserInfoURL + identityProviderOpenIDConfig.ValidateSignature = addIdentityProviderReq.ValidateSignature + identityProviderOpenIDConfig.DefaultScope = addIdentityProviderReq.DefaultScope + + if len(strings.TrimSpace(addIdentityProviderReq.LogoutURL)) > 0 { + identityProviderOpenIDConfig.BackchannelSupported = true + } else { + identityProviderOpenIDConfig.BackchannelSupported = false + } + + identityProviderOpenIDConfig.DisableUserInfo = addIdentityProviderReq.DisableUserInfo + identityProviderOpenIDConfig.Issuer = addIdentityProviderReq.Issuer + + if len(strings.TrimSpace(addIdentityProviderReq.JWKSURL)) > 0 { + identityProviderOpenIDConfig.UseJWKSURL = true + } else { + identityProviderOpenIDConfig.UseJWKSURL = false + } + + identityProviderOpenIDConfig.SyncMode = "IMPORT" + identityProviderOpenIDConfig.ClientAuthMethod = "client_secret_post" + identityProviderOpenIDConfig.HideOnLoginPage = true + + // Fetch admin token from keycloak + t, status, _, err := getAdminToken() + if err != nil { + m := fmt.Sprintf("Failed to fetch the admin token from keycloak; Failed to create identity provider for %v", organizationID) + common.HandleError(w, status, m, err) + return + } + + // Constructing the request payload for creating identity provider + var identityProviderRepresentation = org.IdentityProviderRepresentation{ + Config: identityProviderOpenIDConfig, + Alias: o.ID.Hex(), + ProviderID: "oidc", + AuthenticateByDefault: false, + Enabled: true, + FirstBrokerLoginFlowAlias: iamConfig.ExternalIdentityProvidersConfiguration.IdentityProviderCustomerAutoLinkFlowName, + LinkOnly: false, + AddReadTokenRoleOnCreate: false, + PostBrokerLoginFlowAlias: "", + StoreToken: false, + TrustEmail: false, + } + + // Add identity provider to iGrant.io IAM + status, _, err = addIdentityProvider(identityProviderRepresentation, t.AccessToken) + if err != nil { + m := fmt.Sprintf("Failed to create external identity provider in keycloak; Failed to create identity provider for %v", organizationID) + common.HandleError(w, status, m, err) + return + } + + // Save the identity provider details to the database + o, err = org.UpdateIdentityProviderByOrgID(organizationID, identityProviderRepresentation) + if err != nil { + m := fmt.Sprintf("Failed to update IDP config to database; Failed to create identity provider for %v", organizationID) + common.HandleError(w, status, m, err) + return + } + + // Update external identity provider available status + o, err = org.UpdateExternalIdentityProviderAvailableStatus(organizationID, true) + if err != nil { + m := fmt.Sprintf("Failed to update external identity provider available status; Failed to create identity provider for %v", organizationID) + common.HandleError(w, status, m, err) + return + } + + // FIX ME : Is this right practice to do it anonymous function executed in a separate thread ? + go func() { + // Create a client to manage the user session from external identity provider + + // ID for a custom authentication flow created in the keycloak to manage the conversion of external access token to internal (iGrant.io) authorization code + var IDPCustomKeycloakAuthenticationFlow = iamConfig.ExternalIdentityProvidersConfiguration.IdentityProviderCustomerAuthenticationFlowID + + // Construct the request payload to create OpenID client + var keycloakOpenIDClient = org.KeycloakOpenIDClient{ + Access: org.KeycloakOpenIDClientAccess{ + Configure: true, + View: true, + Manage: true, + }, + AlwaysDisplayInConsole: false, + Attributes: org.KeycloakOpenIDClientAttributes{ + BackchannelLogoutRevokeOfflineTokens: "true", + BackchannelLogoutSessionRequired: "true", + BackchannelLogoutURL: identityProviderOpenIDConfig.LogoutURL, + ClientCredentialsUseRefreshToken: "false", + DisplayOnConsentScreen: "false", + ExcludeSessionStateFromAuthResponse: "false", + SamlAssertionSignature: "false", + SamlAuthnstatement: "false", + SamlClientSignature: "false", + SamlEncrypt: "false", + SamlForcePostBinding: "false", + SamlMultivaluedRoles: "false", + SamlOnetimeuseCondition: "false", + SamlServerSignature: "false", + SamlServerSignatureKeyinfoExt: "false", + SamlForceNameIDFormat: "false", + TLSClientCertificateBoundAccessTokens: "false", + }, + AuthenticationFlowBindingOverrides: org.KeycloakOpenIDClientAuthenticationFlowBindingOverrides{ + Browser: IDPCustomKeycloakAuthenticationFlow, + DirectGrant: IDPCustomKeycloakAuthenticationFlow, + }, + BearerOnly: false, + ClientAuthenticatorType: "client-secret", + ClientID: o.ID.Hex(), + ConsentRequired: false, + DefaultClientScopes: []string{ + "web-origins", + "role_list", + "profile", + "roles", + "email", + }, + DirectAccessGrantsEnabled: true, + Enabled: true, + FrontchannelLogout: false, + FullScopeAllowed: true, + ImplicitFlowEnabled: false, + NodeReRegistrationTimeout: -1, + NotBefore: 0, + OptionalClientScopes: []string{ + "address", + "phone", + "offline_access", + "microprofile-jwt", + }, + Protocol: "openid-connect", + PublicClient: false, + RedirectUris: []string{}, + ServiceAccountsEnabled: false, + StandardFlowEnabled: true, + SurrogateAuthRequired: false, + WebOrigins: []string{}, + } + + // Add OpenID client to iGrant.io IAM + status, _, err = addOpenIDClient(keycloakOpenIDClient, t.AccessToken) + if err != nil { + m := fmt.Sprintf("Failed to create OpenID client in keycloak; Failed to create identity provider for %v", organizationID) + log.Println(m) + return + } + + // Save the OpenID client details to the database + o, err = org.UpdateOpenIDClientByOrgID(organizationID, keycloakOpenIDClient) + if err != nil { + m := fmt.Sprintf("Failed to update OpenID client config to database; Failed to create identity provider for %v", organizationID) + log.Println(m) + return + } + + }() + + response, _ := json.Marshal(o.IdentityProviderRepresentation.Config) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusCreated) + w.Write(response) + +} diff --git a/src/handlerv2/createapikey_handler.go b/src/handlerv2/createapikey_handler.go new file mode 100644 index 0000000..c900c1f --- /dev/null +++ b/src/handlerv2/createapikey_handler.go @@ -0,0 +1,44 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/apikey" + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/token" + "github.com/bb-consent/api/src/user" +) + +type apiKeyResponse struct { + User string + APIKey string +} + +// CreateAPIKey Create the API key for the user +func CreateAPIKey(w http.ResponseWriter, r *http.Request) { + userID := token.GetUserID(r) + + key, err := apikey.Create(userID) + if err != nil { + m := fmt.Sprintf("Failed to create apiKey for user:%v err:%v", token.GetUserName(r), err) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + err = user.UpdateAPIKey(userID, key) + if err != nil { + m := fmt.Sprintf("Failed to store apiKey for user:%v err:%v", token.GetUserName(r), err) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + apiKeyResp := apiKeyResponse{token.GetUserName(r), key} + response, _ := json.Marshal(apiKeyResp) + + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/handlerv2/createwebhook_handler.go b/src/handlerv2/createwebhook_handler.go new file mode 100644 index 0000000..1bb3a0d --- /dev/null +++ b/src/handlerv2/createwebhook_handler.go @@ -0,0 +1,164 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "strconv" + "strings" + "time" + + "github.com/asaskevich/govalidator" + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + wh "github.com/bb-consent/api/src/webhooks" +) + +// createWebhookReq Defines the request payload structure for creating a webhook for an organisation +type createWebhookReq struct { + PayloadURL string `valid:"required,url"` // Webhook endpoint URL + SubscribedEvents []string `valid:"required"` // Subscribed events + ContentType string `valid:"required"` // Data format for the webhook payload + Disabled bool // Disabled or not + SecretKey string // For calculating SHA256 HMAC to verify data integrity and authenticity + SkipSSLVerification bool // Skip SSL certificate verification or not (expiry is checked) +} + +// uniqueSlice Filter out all the duplicate strings and returns the unique slice +func uniqueSlice(inputSlice []string) []string { + keys := make(map[string]bool) + list := []string{} + for _, entry := range inputSlice { + if _, value := keys[entry]; !value { + keys[entry] = true + list = append(list, entry) + } + } + return list +} + +// CreateWebhook Creates a webhook endpoint for an organisation +func CreateWebhook(w http.ResponseWriter, r *http.Request) { + var requestPayload createWebhookReq + + // Reading request body as bytes and unmarshalling to a struct + b, _ := ioutil.ReadAll(r.Body) + defer r.Body.Close() + json.Unmarshal(b, &requestPayload) + + organizationID := r.Header.Get(config.OrganizationId) + + // Validating request payload struct + _, err := govalidator.ValidateStruct(requestPayload) + if err != nil { + log.Printf("Missing mandatory params; Failed to create webhook for organisation: %v", organizationID) + common.HandleError(w, http.StatusBadRequest, err.Error(), err) + return + } + + // Validating the given organisation ID + _, err = org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to get organization: %v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Check if the webhook endpoint contains http:// or https:// + if !(strings.HasPrefix(requestPayload.PayloadURL, "https://") || strings.HasPrefix(requestPayload.PayloadURL, "http://")) { + m := fmt.Sprintf("Please prefix the endpoint URL with https:// or http://; Failed to create webhook for organisation:%v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + sanitizedOrgId := common.Sanitize(organizationID) + sanitizedPayloadURL := common.Sanitize(requestPayload.PayloadURL) + + // Check if webhook with provided payload URL already exists + count, err := wh.GetWebhookCountByPayloadURL(sanitizedOrgId, sanitizedPayloadURL) + if err != nil { + m := fmt.Sprintf("Failed to create webhook for organisation:%v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + if count > 0 { + m := fmt.Sprintf("Webhook with provided payload URL already exists; Failed to create webhook for organisation:%v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Check if subscribed event type(s) array is empty + if len(requestPayload.SubscribedEvents) == 0 { + m := fmt.Sprintf("Provide atleast 1 event type; Failed to create webhook for organisation:%v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Check if subscribed event type(s) contains duplicates + requestPayload.SubscribedEvents = uniqueSlice(requestPayload.SubscribedEvents) + + // Check the subscribed event type ID(s) provided is valid + var isValidSubscribedEvents bool + for _, subscribedEventType := range requestPayload.SubscribedEvents { + isValidSubscribedEvents = false + for _, eventType := range wh.EventTypes { + if subscribedEventType == eventType { + isValidSubscribedEvents = true + break + } + } + + if !isValidSubscribedEvents { + break + } + } + + if !isValidSubscribedEvents { + m := fmt.Sprintf("Please provide a valid event type; Failed to create webhook for organisation:%v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Check if the content type ID provided is valid + isValidContentType := false + for _, payloadContentType := range wh.PayloadContentTypes { + if requestPayload.ContentType == payloadContentType { + isValidContentType = true + } + } + + if !isValidContentType { + m := fmt.Sprintf("Please provide a valid content type; Failed to create webhook for organisation:%v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Constructing webhook structure required for adding it to database + webhook := wh.Webhook{ + OrgID: organizationID, + PayloadURL: strings.TrimSpace(requestPayload.PayloadURL), + ContentType: requestPayload.ContentType, + SubscribedEvents: requestPayload.SubscribedEvents, + Disabled: requestPayload.Disabled, + SecretKey: strings.TrimSpace(requestPayload.SecretKey), + SkipSSLVerification: requestPayload.SkipSSLVerification, + TimeStamp: strconv.FormatInt(time.Now().UTC().Unix(), 10), + } + + // Creating webhook + webhook, err = wh.CreateWebhook(webhook) + if err != nil { + m := fmt.Sprintf("Failed to create webhook for organisation:%v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + response, _ := json.Marshal(webhook) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusCreated) + w.Write(response) + +} diff --git a/src/handlerv2/deleteapikey_handler.go b/src/handlerv2/deleteapikey_handler.go new file mode 100644 index 0000000..9282f91 --- /dev/null +++ b/src/handlerv2/deleteapikey_handler.go @@ -0,0 +1,24 @@ +package handlerv2 + +import ( + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/token" + "github.com/bb-consent/api/src/user" +) + +// DeleteAPIKey User forgot the password, need to reset the password +func DeleteAPIKey(w http.ResponseWriter, r *http.Request) { + userID := token.GetUserID(r) + + err := user.UpdateAPIKey(userID, "") + if err != nil { + m := fmt.Sprintf("Failed to remove apiKey for user:%v err:%v", token.GetUserName(r), err) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + w.WriteHeader(http.StatusOK) +} diff --git a/src/handlerv2/deletedataagreement_handler.go b/src/handlerv2/deletedataagreement_handler.go new file mode 100644 index 0000000..e91fa4e --- /dev/null +++ b/src/handlerv2/deletedataagreement_handler.go @@ -0,0 +1,90 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + "github.com/gorilla/mux" +) + +func deletePurposeIDFromTemplate(purposeID string, orgID string, templates []org.Template) error { + for _, t := range templates { + for _, p := range t.PurposeIDs { + if p == purposeID { + var template org.Template + template.Consent = t.Consent + template.ID = t.ID + for _, p := range t.PurposeIDs { + if p != purposeID { + template.PurposeIDs = append(template.PurposeIDs, p) + } + } + _, err := org.DeleteTemplates(orgID, t) + if err != nil { + fmt.Printf("Failed to delete template: %v from organization: %v", t.ID, orgID) + return err + } + if len(template.PurposeIDs) == 0 { + continue + } + err = org.AddTemplates(orgID, template) + if err != nil { + fmt.Printf("Failed to add template: %v from organization: %v", t.ID, orgID) + return err + } + continue + } + } + } + return nil +} + +// DeleteDataAgreement Deletes the given data agreement by ID +func DeleteDataAgreement(w http.ResponseWriter, r *http.Request) { + organizationID := r.Header.Get(config.OrganizationId) + purposeID := mux.Vars(r)[config.DataAgreementId] + + o, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to fetch organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + var purposeToDelete org.Purpose + for _, p := range o.Purposes { + if p.ID == purposeID { + purposeToDelete = p + } + } + + if purposeToDelete == (org.Purpose{}) { + m := fmt.Sprintf("Failed to find purpose with ID: %v in organization: %v", purposeID, o.Name) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + //TODO: Before we delete purpose, need to remove the purpose from the templates + err = deletePurposeIDFromTemplate(purposeID, o.ID.Hex(), o.Templates) + if err != nil { + m := fmt.Sprintf("Failed to update template for organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + orgResp, err := org.DeletePurposes(o.ID.Hex(), purposeToDelete) + if err != nil { + m := fmt.Sprintf("Failed to delete purpose: %v from organization: %v", purposeID, o.Name) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + response, _ := json.Marshal(organization{orgResp}) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusCreated) + w.Write(response) +} diff --git a/src/handlerv2/deletedataattributebyid_handler.go b/src/handlerv2/deletedataattributebyid_handler.go new file mode 100644 index 0000000..a415ca4 --- /dev/null +++ b/src/handlerv2/deletedataattributebyid_handler.go @@ -0,0 +1,50 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + "github.com/gorilla/mux" +) + +// DeleteDataAttributeById Deletes an organization data attribute +func DeleteDataAttributeById(w http.ResponseWriter, r *http.Request) { + organizationID := r.Header.Get(config.OrganizationId) + templateID := mux.Vars(r)[config.DataAttributeId] + + o, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to fetch organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + var templateToDelete org.Template + for _, t := range o.Templates { + if t.ID == templateID { + templateToDelete = t + } + } + + if templateToDelete.ID != templateID { + m := fmt.Sprintf("Failed to find template with ID: %v in organization: %v", templateID, o.Name) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + orgResp, err := org.DeleteTemplates(o.ID.Hex(), templateToDelete) + if err != nil { + m := fmt.Sprintf("Failed to delete template: %v from organization: %v", templateID, o.Name) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + response, _ := json.Marshal(organization{orgResp}) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusCreated) + w.Write(response) +} diff --git a/src/handlerv2/deleteidentityprovider_handler.go b/src/handlerv2/deleteidentityprovider_handler.go new file mode 100644 index 0000000..5a54ff8 --- /dev/null +++ b/src/handlerv2/deleteidentityprovider_handler.go @@ -0,0 +1,181 @@ +package handlerv2 + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" +) + +// Helper function to delete identity provider to iGrant.io IAM +func deleteIdentityProvider(identityProviderAlias string, adminToken string) (int, iamError, error) { + var e iamError + var status = http.StatusInternalServerError + req, err := http.NewRequest("DELETE", iamConfig.URL+"/admin/realms/"+iamConfig.Realm+"/identity-provider/instances/"+identityProviderAlias, nil) + if err != nil { + return status, e, err + } + + req.Header.Add("Authorization", "Bearer "+adminToken) + req.Header.Add(config.ContentTypeHeader, config.ContentTypeJSON) + + client := http.Client{ + Timeout: timeout, + } + resp, err := client.Do(req) + if err != nil { + return status, e, err + } + + if resp.StatusCode != http.StatusNoContent { + body, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + + type errorMsg struct { + ErrorMessage string `json:"errorMessage"` + } + var errMsg errorMsg + json.Unmarshal(body, &errMsg) + e.Error = errMsg.ErrorMessage + e.ErrorType = "Identity provider delete failed" + return resp.StatusCode, e, errors.New("failed to delete identity provider") + } + + defer resp.Body.Close() + + return resp.StatusCode, e, err +} + +// Helper function to delete OpenID client to manage login sessions for the external identity provider +func deleteOpenIDClient(clientUUID string, adminToken string) (int, iamError, error) { + + var e iamError + var status = http.StatusInternalServerError + req, err := http.NewRequest("DELETE", iamConfig.URL+"/admin/realms/"+iamConfig.Realm+"/clients/"+clientUUID, nil) + if err != nil { + return status, e, err + } + + req.Header.Add("Authorization", "Bearer "+adminToken) + req.Header.Add(config.ContentTypeHeader, config.ContentTypeJSON) + + client := http.Client{ + Timeout: timeout, + } + resp, err := client.Do(req) + if err != nil { + return status, e, err + } + + if resp.StatusCode != http.StatusNoContent { + body, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + + type errorMsg struct { + ErrorMessage string `json:"errorMessage"` + } + var errMsg errorMsg + json.Unmarshal(body, &errMsg) + e.Error = errMsg.ErrorMessage + e.ErrorType = "OpenID client delete failed" + return resp.StatusCode, e, errors.New("failed to delete OpenID client") + } + + body, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + var responseBody interface{} + json.Unmarshal(body, &responseBody) + + return resp.StatusCode, e, err + +} + +// DeleteIdentityProvider Delete external identity provider for an organisation +func DeleteIdentityProvider(w http.ResponseWriter, r *http.Request) { + + // Get the org ID and fetch the organization from the db. + organizationID := r.Header.Get(config.OrganizationId) + o, err := org.Get(organizationID) + + if err != nil { + m := fmt.Sprintf("Failed to fetch org; Failed to delete identity provider for %v", organizationID) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + if !o.ExternalIdentityProviderAvailable { + m := fmt.Sprintf("External IDP provider doesn't exist; Try to create instead of delete; Failed to delete identity provider for %v", organizationID) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + // Fetch admin token from keycloak + t, status, _, err := getAdminToken() + if err != nil { + m := fmt.Sprintf("Failed to fetch the admin token from keycloak; Failed to delete identity provider for %v", organizationID) + common.HandleError(w, status, m, err) + return + } + + // Delete identity provider in IAM + status, _, err = deleteIdentityProvider(o.IdentityProviderRepresentation.Alias, t.AccessToken) + if err != nil { + m := fmt.Sprintf("Failed to delete external identity provider in keycloak; Failed to delete identity provider for %v", organizationID) + common.HandleError(w, status, m, err) + return + } + + // Update external identity provider available status + o, err = org.UpdateExternalIdentityProviderAvailableStatus(organizationID, false) + if err != nil { + m := fmt.Sprintf("Failed to update external identity provider available status; Failed to delete identity provider for %v", organizationID) + common.HandleError(w, status, m, err) + return + } + + // Delete the identity provider details from the database + o, err = org.DeleteIdentityProviderByOrgID(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to delete IDP config to database; Failed to delete identity provider for %v", organizationID) + common.HandleError(w, status, m, err) + return + } + + // FIX ME : Is this right practice to do it anonymous function executed in a separate thread ? + go func() { + + // Fetch OpenID client UUID from IAM + openIDClientUUID, _, _, err := getClientsInRealm(o.KeycloakOpenIDClient.ClientID, t.AccessToken) + if err != nil { + m := fmt.Sprintf("Failed to fetch OpenID client UUID from keycloak; Failed to delete identity provider for %v", organizationID) + log.Println(m) + return + } + + // Delete OpenID client in iGrant.io IAM + _, _, err = deleteOpenIDClient(openIDClientUUID, t.AccessToken) + if err != nil { + m := fmt.Sprintf("Failed to delete external identity provider in keycloak; Failed to delete identity provider for %v", organizationID) + log.Println(m) + return + } + + // Delete the OpenID client details from the database + _, err = org.DeleteOpenIDClientByOrgID(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to delete OpenID config to database; Failed to delete identity provider for %v", organizationID) + log.Println(m) + return + } + + }() + + w.WriteHeader(http.StatusNoContent) + w.Write(nil) +} diff --git a/src/handlerv2/deleteuser_handler.go b/src/handlerv2/deleteuser_handler.go new file mode 100644 index 0000000..b76f6a3 --- /dev/null +++ b/src/handlerv2/deleteuser_handler.go @@ -0,0 +1,14 @@ +package handlerv2 + +import ( + "net/http" + + "github.com/bb-consent/api/src/config" +) + +func DeleteUser(w http.ResponseWriter, r *http.Request) { + + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + +} diff --git a/src/handlerv2/deletewebhook_handler.go b/src/handlerv2/deletewebhook_handler.go new file mode 100644 index 0000000..94ff4dd --- /dev/null +++ b/src/handlerv2/deletewebhook_handler.go @@ -0,0 +1,50 @@ +package handlerv2 + +import ( + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + wh "github.com/bb-consent/api/src/webhooks" + "github.com/gorilla/mux" +) + +// DeleteWebhook Deletes a webhook for an organisation +func DeleteWebhook(w http.ResponseWriter, r *http.Request) { + // Reading URL parameters + organizationID := r.Header.Get(config.OrganizationId) + webhookID := mux.Vars(r)[config.WebhookId] + + // Validating the given organisation ID + _, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to get organization: %v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + sanitizedOrgId := common.Sanitize(organizationID) + sanitizedWebhookId := common.Sanitize(webhookID) + + // Validating the given webhook ID for an organisation + _, err = wh.GetByOrgID(sanitizedWebhookId, sanitizedOrgId) + if err != nil { + m := fmt.Sprintf("Failed to get webhook:%v for organisation: %v", webhookID, organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Deleting webhook + err = wh.DeleteWebhook(webhookID) + if err != nil { + m := fmt.Sprintf("Failed to delete webhook:%v for organisation: %v", webhookID, organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusNoContent) + +} diff --git a/src/handlerv2/getallwebhooks_handler.go b/src/handlerv2/getallwebhooks_handler.go new file mode 100644 index 0000000..553f02c --- /dev/null +++ b/src/handlerv2/getallwebhooks_handler.go @@ -0,0 +1,84 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + wh "github.com/bb-consent/api/src/webhooks" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +// WebhookWithLastDeliveryStatus Defines webhook structure along with last delivery status +type WebhookWithLastDeliveryStatus struct { + ID primitive.ObjectID `bson:"_id,omitempty"` // Webhook ID + PayloadURL string // Webhook payload URL + Disabled bool // Disabled or not + TimeStamp string // UTC timestamp + IsLastDeliverySuccess bool // Indicates whether last payload delivery to webhook was success or not +} + +// GetAllWebhooks Gets all webhooks for an organisation +func GetAllWebhooks(w http.ResponseWriter, r *http.Request) { + // Reading URL parameters + organizationID := r.Header.Get(config.OrganizationId) + + // Validating the given organisation ID + _, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to get organization: %v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + sanitizedOrgId := common.Sanitize(organizationID) + + // Fetching all the webhooks for an organisation + webhooks, err := wh.GetAllWebhooksByOrgID(sanitizedOrgId) + if err != nil { + m := fmt.Sprintf("Failed to fetch webhooks for organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + var updatedWebhooks []WebhookWithLastDeliveryStatus + + updatedWebhooks = make([]WebhookWithLastDeliveryStatus, 0) + + for _, webhook := range webhooks { + + // Fetching the last delivery to the webhook and retrieving the delivery status + isLastDeliverySuccess := false + lastDelivery, err := wh.GetLastWebhookDelivery(webhook.ID.Hex()) + if err != nil { + // There is no payload delivery yet ! + isLastDeliverySuccess = true + } else { + // if the last payload delivery is completed and response status code is within 2XX range + if lastDelivery.Status == wh.DeliveryStatus[wh.DeliveryStatusCompleted] { + if (lastDelivery.ResponseStatusCode >= 200 && lastDelivery.ResponseStatusCode <= 208) || lastDelivery.ResponseStatusCode == 226 { + isLastDeliverySuccess = true + } + } + + } + + updatedWebhook := WebhookWithLastDeliveryStatus{ + ID: webhook.ID, + PayloadURL: webhook.PayloadURL, + Disabled: webhook.Disabled, + TimeStamp: webhook.TimeStamp, + IsLastDeliverySuccess: isLastDeliverySuccess, + } + + updatedWebhooks = append(updatedWebhooks, updatedWebhook) + } + + response, _ := json.Marshal(updatedWebhooks) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/handlerv2/getapikey_handler.go b/src/handlerv2/getapikey_handler.go new file mode 100644 index 0000000..de3c158 --- /dev/null +++ b/src/handlerv2/getapikey_handler.go @@ -0,0 +1,31 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/token" + "github.com/bb-consent/api/src/user" +) + +// GetAPIKey Get the API key from user +func GetAPIKey(w http.ResponseWriter, r *http.Request) { + userID := token.GetUserID(r) + + apiKey, err := user.GetAPIKey(userID) + if err != nil { + m := fmt.Sprintf("Failed to get apiKey for user:%v err:%v", token.GetUserName(r), err) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + apiKeyResp := apiKeyResponse{token.GetUserName(r), apiKey} + response, _ := json.Marshal(apiKeyResp) + + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/handlerv2/getdataagreementbyid_handler.go b/src/handlerv2/getdataagreementbyid_handler.go new file mode 100644 index 0000000..db10286 --- /dev/null +++ b/src/handlerv2/getdataagreementbyid_handler.go @@ -0,0 +1,54 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + "github.com/gorilla/mux" +) + +// GetDataAgreementById Get a data agreement by ID +func GetDataAgreementById(w http.ResponseWriter, r *http.Request) { + orgID := r.Header.Get(config.OrganizationId) + purposeID := mux.Vars(r)[config.DataAgreementId] + + o, err := org.Get(orgID) + if err != nil { + m := fmt.Sprintf("Failed to get organization: %v", orgID) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + type purposeTemplates struct { + ID string + Consent string + } + + type purposeDetails struct { + Purpose org.Purpose + Templates []purposeTemplates + } + var pDetails purposeDetails + for _, p := range o.Purposes { + if p.ID == purposeID { + pDetails.Purpose = p + } + } + + for _, t := range o.Templates { + for _, pID := range t.PurposeIDs { + if pID == purposeID { + pDetails.Templates = append(pDetails.Templates, purposeTemplates{ID: t.ID, Consent: t.Consent}) + } + } + } + + response, _ := json.Marshal(pDetails) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/handlerv2/getdataattributes_handler.go b/src/handlerv2/getdataattributes_handler.go new file mode 100644 index 0000000..bee2114 --- /dev/null +++ b/src/handlerv2/getdataattributes_handler.go @@ -0,0 +1,33 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" +) + +type getTemplatesResp struct { + OrgID string + Templates []org.Template +} + +// GetDataAttributes Gets an organization data attributes +func GetDataAttributes(w http.ResponseWriter, r *http.Request) { + organizationID := r.Header.Get(config.OrganizationId) + + o, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to fetch organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + response, _ := json.Marshal(getTemplatesResp{OrgID: o.ID.Hex(), Templates: o.Templates}) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusCreated) + w.Write(response) +} diff --git a/src/handlerv2/getglobalpolicyconfigurations_handler.go b/src/handlerv2/getglobalpolicyconfigurations_handler.go new file mode 100644 index 0000000..ce4fb68 --- /dev/null +++ b/src/handlerv2/getglobalpolicyconfigurations_handler.go @@ -0,0 +1,73 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + "github.com/bb-consent/api/src/orgtype" +) + +type globalPolicyConfigurationResp struct { + PolicyURL string + DataRetention org.DataRetention + Jurisdiction string + Disclosure string + Type orgtype.OrgType + Restriction string + Shared3PP bool +} + +// GetGlobalPolicyConfiguration Handler to get global policy configurations +func GetGlobalPolicyConfiguration(w http.ResponseWriter, r *http.Request) { + organizationID := r.Header.Get(config.OrganizationId) + + o, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to fetch organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + // Constructing the response + var resp globalPolicyConfigurationResp + + resp.PolicyURL = o.PolicyURL + resp.DataRetention = o.DataRetention + + if len(strings.TrimSpace(o.Jurisdiction)) == 0 { + resp.Jurisdiction = o.Location + o.Jurisdiction = o.Location + } else { + resp.Jurisdiction = o.Jurisdiction + } + + if len(strings.TrimSpace(o.Disclosure)) == 0 { + resp.Disclosure = "false" + o.Disclosure = "false" + } else { + resp.Disclosure = o.Disclosure + } + + resp.Type = o.Type + resp.Restriction = o.Restriction + resp.Shared3PP = o.Shared3PP + + // Updating global configuration policy with defaults + _, err = org.Update(o) + if err != nil { + m := fmt.Sprintf("Failed to update global configuration with defaults to organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) + +} diff --git a/src/handlerv2/getidentityprovider_handler.go b/src/handlerv2/getidentityprovider_handler.go new file mode 100644 index 0000000..57414a0 --- /dev/null +++ b/src/handlerv2/getidentityprovider_handler.go @@ -0,0 +1,36 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" +) + +// GetIdentityProvider Get external identity provider for an organisation +func GetIdentityProvider(w http.ResponseWriter, r *http.Request) { + + // Get the org ID and fetch the organization from the db. + organizationID := r.Header.Get(config.OrganizationId) + o, err := org.Get(organizationID) + + if err != nil { + m := fmt.Sprintf("Failed to fetch org; Failed to get identity provider for %v", organizationID) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + if !o.ExternalIdentityProviderAvailable { + m := fmt.Sprintf("External IDP provider doesn't exist; Try to create instead of get; Failed to get identity provider for %v", organizationID) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + response, _ := json.Marshal(o.IdentityProviderRepresentation.Config) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/handlerv2/getorganizationusers_handler.go b/src/handlerv2/getorganizationusers_handler.go new file mode 100644 index 0000000..1b9b80e --- /dev/null +++ b/src/handlerv2/getorganizationusers_handler.go @@ -0,0 +1,51 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/user" + "github.com/gorilla/mux" +) + +type orgUser struct { + ID string + Name string + Phone string + Email string +} +type orgUsers struct { + Users []orgUser + Links common.PaginationLinks +} + +// GetOrganizationUsers Gets list of organization users +func GetOrganizationUsers(w http.ResponseWriter, r *http.Request) { + organizationID := mux.Vars(r)["organizationID"] + + startID, limit := common.ParsePaginationQueryParameters(r) + if limit == 0 { + limit = 20 + } + + users, lastID, err := user.GetOrgSubscribeUsers(organizationID, startID, limit) + + if err != nil { + m := fmt.Sprintf("Failed to get user subscribed to organization :%v", organizationID) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + var ou orgUsers + for _, u := range users { + ou.Users = append(ou.Users, orgUser{ID: u.ID.Hex(), Name: u.Name, Phone: u.Phone, Email: u.Email}) + } + + ou.Links = common.CreatePaginationLinks(r, startID, lastID, limit) + response, _ := json.Marshal(ou) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.Write(response) +} diff --git a/src/handlerv2/getrecentwebhookdeliveries_handler.go b/src/handlerv2/getrecentwebhookdeliveries_handler.go new file mode 100644 index 0000000..3d148d8 --- /dev/null +++ b/src/handlerv2/getrecentwebhookdeliveries_handler.go @@ -0,0 +1,97 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + wh "github.com/bb-consent/api/src/webhooks" + "github.com/gorilla/mux" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +// recentWebhookDelivery Defines the structure for recent webhook delivery +type recentWebhookDelivery struct { + ID primitive.ObjectID `bson:"_id,omitempty"` // Webhook delivery ID + WebhookID string // Webhook ID + ResponseStatusCode int // HTTP response status code + ResponseStatusStr string // HTTP response status string + TimeStamp string // UTC timestamp when webhook execution started + Status string // Status of webhook delivery for e.g. failed or completed + StatusDescription string // Describe the status for e.g. Reason for failure +} + +type recentWebhookDeliveryResp struct { + WebhookDeliveries []recentWebhookDelivery + Links common.PaginationLinks +} + +// GetRecentWebhookDeliveries Gets the recent webhook deliveries limited by `x` records +func GetRecentWebhookDeliveries(w http.ResponseWriter, r *http.Request) { + // Reading the URL parameters + organizationID := r.Header.Get(config.OrganizationId) + webhookID := mux.Vars(r)[config.WebhookId] + + startID, limit := common.ParsePaginationQueryParameters(r) + if limit == 0 { + limit = 50 + } + + // Validating the given organisation ID + _, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to get organization: %v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + sanitizedOrgId := common.Sanitize(organizationID) + sanitizedWebhookId := common.Sanitize(webhookID) + + // Validating the given webhook ID for an organisation + webhook, err := wh.GetByOrgID(sanitizedWebhookId, sanitizedOrgId) + if err != nil { + m := fmt.Sprintf("Failed to get webhook:%v for organisation: %v", webhookID, organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Get all the recent webhook deliveries + recentWebhookDeliveries, lastID, err := wh.GetAllDeliveryByWebhookID(webhook.ID.Hex(), startID, limit) + if err != nil { + m := fmt.Sprintf("Failed to fetch recent payload deliveries for webhook:%v for organisation: %v", webhookID, organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + // Constructing the response + var resp recentWebhookDeliveryResp + + resp.WebhookDeliveries = make([]recentWebhookDelivery, 0) + + for _, wd := range recentWebhookDeliveries { + + tempRecentWebhookDelivery := recentWebhookDelivery{ + ID: wd.ID, + WebhookID: wd.WebhookID, + ResponseStatusCode: wd.ResponseStatusCode, + ResponseStatusStr: wd.ResponseStatusStr, + TimeStamp: wd.ExecutionStartTimeStamp, + Status: wd.Status, + StatusDescription: wd.StatusDescription, + } + + resp.WebhookDeliveries = append(resp.WebhookDeliveries, tempRecentWebhookDelivery) + } + + resp.Links = common.CreatePaginationLinks(r, startID, lastID, limit) + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) + +} diff --git a/src/handlerv2/getrecentwebhookdeliverybyid_handler.go b/src/handlerv2/getrecentwebhookdeliverybyid_handler.go new file mode 100644 index 0000000..b9f5d70 --- /dev/null +++ b/src/handlerv2/getrecentwebhookdeliverybyid_handler.go @@ -0,0 +1,83 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + wh "github.com/bb-consent/api/src/webhooks" + "github.com/gorilla/mux" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type webhookDeliveryResp struct { + ID primitive.ObjectID `bson:"_id,omitempty"` + RequestHeaders map[string][]string // HTTP headers posted to webhook endpoint + RequestPayload interface{} // JSON payload posted to webhook endpoint + ResponseHeaders map[string][]string // HTTP response headers received from webhook endpoint + ResponseBody string // HTTP response body received from webhook endpoint in bytes + ResponseStatusCode int // HTTP response status code + ResponseStatusStr string // HTTP response status string + ExecutionStartTimeStamp string // UTC timestamp when webhook execution started + ExecutionEndTimeStamp string // UTC timestamp when webhook execution ended + Status string // Status of webhook delivery for e.g. failed or completed + StatusDescription string // Describe the status for e.g. Reason for failure +} + +// GetRecentWebhookDeliveryById Gets the payload delivery details for a webhook by ID +func GetRecentWebhookDeliveryById(w http.ResponseWriter, r *http.Request) { + // Reading the URL parameters + organizationID := r.Header.Get(config.OrganizationId) + webhookID := mux.Vars(r)[config.WebhookId] + deliveryID := mux.Vars(r)[config.WebhookDeliveryId] + + // Validating the given organisation ID + _, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to get organization: %v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + sanitizedOrgId := common.Sanitize(organizationID) + sanitizedWebhookId := common.Sanitize(webhookID) + sanitizedDeliveryId := common.Sanitize(deliveryID) + + // Validating the given webhook ID for an organisation + webhook, err := wh.GetByOrgID(sanitizedWebhookId, sanitizedOrgId) + if err != nil { + m := fmt.Sprintf("Failed to get webhook:%v for organisation: %v", webhookID, organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Get the webhook delivery by ID + webhookDelivery, err := wh.GetWebhookDeliveryByID(webhook.ID.Hex(), sanitizedDeliveryId) + if err != nil { + m := fmt.Sprintf("Failed to get delivery details by ID:%v for webhook:%v for organisation: %v", deliveryID, webhookID, organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + resp := webhookDeliveryResp{ + ID: webhookDelivery.ID, + RequestHeaders: webhookDelivery.RequestHeaders, + RequestPayload: webhookDelivery.RequestPayload, + ResponseHeaders: webhookDelivery.ResponseHeaders, + ResponseBody: webhookDelivery.ResponseBody, + ResponseStatusCode: webhookDelivery.ResponseStatusCode, + ResponseStatusStr: webhookDelivery.ResponseStatusStr, + ExecutionStartTimeStamp: webhookDelivery.ExecutionStartTimeStamp, + ExecutionEndTimeStamp: webhookDelivery.ExecutionEndTimeStamp, + Status: webhookDelivery.Status, + StatusDescription: webhookDelivery.StatusDescription, + } + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/handlerv2/getuser_handler.go b/src/handlerv2/getuser_handler.go new file mode 100644 index 0000000..726468d --- /dev/null +++ b/src/handlerv2/getuser_handler.go @@ -0,0 +1,14 @@ +package handlerv2 + +import ( + "net/http" + + "github.com/bb-consent/api/src/config" +) + +func GetUser(w http.ResponseWriter, r *http.Request) { + + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + +} diff --git a/src/handlerv2/getwebhookeventtypes_handler.go b/src/handlerv2/getwebhookeventtypes_handler.go new file mode 100644 index 0000000..1e0eaf8 --- /dev/null +++ b/src/handlerv2/getwebhookeventtypes_handler.go @@ -0,0 +1,29 @@ +package handlerv2 + +import ( + "encoding/json" + "net/http" + + "github.com/bb-consent/api/src/config" + wh "github.com/bb-consent/api/src/webhooks" +) + +// WebhookEventTypesResp Define response structure for webhook event types +type WebhookEventTypesResp struct { + EventTypes []string +} + +// GetWebhookEventTypes List available webhook event types +func GetWebhookEventTypes(w http.ResponseWriter, r *http.Request) { + var webhookEventTypesResp WebhookEventTypesResp + + for _, eventType := range wh.EventTypes { + webhookEventTypesResp.EventTypes = append(webhookEventTypesResp.EventTypes, eventType) + } + + response, _ := json.Marshal(webhookEventTypesResp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) + +} diff --git a/src/handlerv2/getwebhookpayloadcontenttypes_handler.go b/src/handlerv2/getwebhookpayloadcontenttypes_handler.go new file mode 100644 index 0000000..3de72d1 --- /dev/null +++ b/src/handlerv2/getwebhookpayloadcontenttypes_handler.go @@ -0,0 +1,28 @@ +package handlerv2 + +import ( + "encoding/json" + "net/http" + + "github.com/bb-consent/api/src/config" + wh "github.com/bb-consent/api/src/webhooks" +) + +// WebhookPayloadContentTypesResp Defines response structure for webhook payload content types +type WebhookPayloadContentTypesResp struct { + ContentTypes []string +} + +// GetWebhookPayloadContentTypes List available webhook payload content types +func GetWebhookPayloadContentTypes(w http.ResponseWriter, r *http.Request) { + var webhookPayloadContentTypesResp WebhookPayloadContentTypesResp + + for _, payloadContentTypes := range wh.PayloadContentTypes { + webhookPayloadContentTypesResp.ContentTypes = append(webhookPayloadContentTypesResp.ContentTypes, payloadContentTypes) + } + + response, _ := json.Marshal(webhookPayloadContentTypesResp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/handlerv2/listdataagreementrevisions_handler.go b/src/handlerv2/listdataagreementrevisions_handler.go new file mode 100644 index 0000000..b07ba30 --- /dev/null +++ b/src/handlerv2/listdataagreementrevisions_handler.go @@ -0,0 +1,17 @@ +package handlerv2 + +import ( + "encoding/json" + "net/http" + + "github.com/bb-consent/api/src/config" +) + +// ListDataAgreementRevisions Gets an organization data agreements revisions +func ListDataAgreementRevisions(w http.ResponseWriter, r *http.Request) { + + response, _ := json.Marshal(getPurposesResp{}) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusCreated) + w.Write(response) +} diff --git a/src/handlerv2/listdataagreements_handler.go b/src/handlerv2/listdataagreements_handler.go new file mode 100644 index 0000000..45e76fe --- /dev/null +++ b/src/handlerv2/listdataagreements_handler.go @@ -0,0 +1,33 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" +) + +type getPurposesResp struct { + OrgID string + Purposes []org.Purpose +} + +// ListDataAgreements Gets an organization data agreements +func ListDataAgreements(w http.ResponseWriter, r *http.Request) { + organizationID := r.Header.Get(config.OrganizationId) + + o, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to fetch organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + response, _ := json.Marshal(getPurposesResp{OrgID: o.ID.Hex(), Purposes: o.Purposes}) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusCreated) + w.Write(response) +} diff --git a/src/handlerv2/orgdeletepolicy_handler.go b/src/handlerv2/orgdeletepolicy_handler.go new file mode 100644 index 0000000..a8417b6 --- /dev/null +++ b/src/handlerv2/orgdeletepolicy_handler.go @@ -0,0 +1,21 @@ +package handlerv2 + +import ( + "encoding/json" + "net/http" + + "github.com/bb-consent/api/src/config" +) + +// OrgDeletePolicy Handler to delete global policy revision +func OrgDeletePolicy(w http.ResponseWriter, r *http.Request) { + + // Constructing the response + var resp globalPolicyConfigurationResp + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) + +} diff --git a/src/handlerv2/orglistpolicy_handler.go b/src/handlerv2/orglistpolicy_handler.go new file mode 100644 index 0000000..5a89e65 --- /dev/null +++ b/src/handlerv2/orglistpolicy_handler.go @@ -0,0 +1,21 @@ +package handlerv2 + +import ( + "encoding/json" + "net/http" + + "github.com/bb-consent/api/src/config" +) + +// OrgListPolicy Handler to list all global policies +func OrgListPolicy(w http.ResponseWriter, r *http.Request) { + + // Constructing the response + var resp globalPolicyConfigurationResp + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) + +} diff --git a/src/handlerv2/orglistpolicyrevisions_handler.go b/src/handlerv2/orglistpolicyrevisions_handler.go new file mode 100644 index 0000000..0c51375 --- /dev/null +++ b/src/handlerv2/orglistpolicyrevisions_handler.go @@ -0,0 +1,21 @@ +package handlerv2 + +import ( + "encoding/json" + "net/http" + + "github.com/bb-consent/api/src/config" +) + +// OrgListPolicyRevisions Handler to list global policy revisions +func OrgListPolicyRevisions(w http.ResponseWriter, r *http.Request) { + + // Constructing the response + var resp globalPolicyConfigurationResp + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) + +} diff --git a/src/handlerv2/pingwebhook_handler.go b/src/handlerv2/pingwebhook_handler.go new file mode 100644 index 0000000..f47f0b9 --- /dev/null +++ b/src/handlerv2/pingwebhook_handler.go @@ -0,0 +1,90 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + wh "github.com/bb-consent/api/src/webhooks" + "github.com/gorilla/mux" +) + +// PingWebhookResp Defines the response structure for webhook status check using ping +type PingWebhookResp struct { + ResponseStatusCode int // HTTP response status code + ResponseStatusStr string // HTTP response status string + ExecutionStartTimeStamp string // UTC timestamp when webhook execution started + ExecutionEndTimeStamp string // UTC timestamp when webhook execution ended + Status string // Status of webhook delivery for e.g. failed or completed + StatusDescription string // Describe the status for e.g. Reason for failure +} + +// PingWebhook Pings webhook payload URL to check the response status code is 200 OK or not +func PingWebhook(w http.ResponseWriter, r *http.Request) { + + // Reading the URL parameters + organizationID := r.Header.Get(config.OrganizationId) + webhookID := mux.Vars(r)[config.WebhookId] + + // Validating the given organisation ID + _, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to get organization: %v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + sanitizedOrgId := common.Sanitize(organizationID) + sanitizedWebhookId := common.Sanitize(webhookID) + + // Validating the given webhook ID for an organisation + webhook, err := wh.GetByOrgID(sanitizedWebhookId, sanitizedOrgId) + if err != nil { + m := fmt.Sprintf("Failed to get webhook:%v for organisation: %v", webhookID, organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Pinging webhook payload URL + _, resp, executionStartTimeStamp, executionEndTimeStamp, err := wh.PingWebhook(webhook) + + if err != nil { + + log.Printf("Error: %v; Failed to ping webhook:%v for organisation: %v", err, webhookID, organizationID) + + // Constructing webhook ping response + pingWebhookResp := PingWebhookResp{ + ExecutionStartTimeStamp: executionStartTimeStamp, + ExecutionEndTimeStamp: executionEndTimeStamp, + Status: wh.DeliveryStatus[wh.DeliveryStatusFailed], + StatusDescription: err.Error(), + } + + response, _ := json.Marshal(pingWebhookResp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) + return + } + + defer resp.Body.Close() + + // Constructing webhook ping response + pingWebhookResp := PingWebhookResp{ + ResponseStatusCode: resp.StatusCode, + ResponseStatusStr: resp.Status, + ExecutionStartTimeStamp: executionStartTimeStamp, + ExecutionEndTimeStamp: executionEndTimeStamp, + Status: wh.DeliveryStatus[wh.DeliveryStatusCompleted], + } + + response, _ := json.Marshal(pingWebhookResp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) + +} diff --git a/src/handlerv2/readdataagreementrevision_handler.go b/src/handlerv2/readdataagreementrevision_handler.go new file mode 100644 index 0000000..ff3c447 --- /dev/null +++ b/src/handlerv2/readdataagreementrevision_handler.go @@ -0,0 +1,17 @@ +package handlerv2 + +import ( + "encoding/json" + "net/http" + + "github.com/bb-consent/api/src/config" +) + +// ReadDataAgreementRevision Gets an organization data agreements revision +func ReadDataAgreementRevision(w http.ResponseWriter, r *http.Request) { + + response, _ := json.Marshal(getPurposesResp{}) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusCreated) + w.Write(response) +} diff --git a/src/handlerv2/redeliverwebhookpayloadbydeliveryid_handler.go b/src/handlerv2/redeliverwebhookpayloadbydeliveryid_handler.go new file mode 100644 index 0000000..264070d --- /dev/null +++ b/src/handlerv2/redeliverwebhookpayloadbydeliveryid_handler.go @@ -0,0 +1,77 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bb-consent/api/src/actionlog" + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + "github.com/bb-consent/api/src/user" + wh "github.com/bb-consent/api/src/webhooks" + "github.com/gorilla/mux" +) + +// RedeliverWebhookPayloadByDeliveryID Redo payload delivery to the webhook +func RedeliverWebhookPayloadByDeliveryID(w http.ResponseWriter, r *http.Request) { + // Reading the URL parameters + organizationID := r.Header.Get(config.OrganizationId) + webhookID := mux.Vars(r)[config.WebhookId] + deliveryID := mux.Vars(r)[config.WebhookDeliveryId] + + // Validating the given organisation ID + _, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to get organization: %v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + sanitizedOrgId := common.Sanitize(organizationID) + sanitizedWebhookId := common.Sanitize(webhookID) + sanitizedDeliveryId := common.Sanitize(deliveryID) + + // Validating the given webhook ID for an organisation + webhook, err := wh.GetByOrgID(sanitizedWebhookId, sanitizedOrgId) + if err != nil { + m := fmt.Sprintf("Failed to get webhook:%v for organisation: %v", webhookID, organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Validating the given delivery ID for a webhook + webhookDelivery, err := wh.GetWebhookDeliveryByID(webhook.ID.Hex(), sanitizedDeliveryId) + if err != nil { + m := fmt.Sprintf("Failed to get delivery details by ID:%v for webhook:%v for organisation: %v", deliveryID, webhookID, organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Converting the webhook payload to bytes + webhookPayloadBytes, err := json.Marshal(webhookDelivery.RequestPayload) + if err != nil { + m := fmt.Sprintf("Failed to convert webhook event data to bytes, error:%v; Failed to redeliver payload for webhook for event:<%s>, user:<%s>, org:<%s>", err.Error(), webhookDelivery.WebhookEventType, webhookDelivery.UserID, organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + // Get the details of who triggered webhook + u, err := user.Get(webhookDelivery.UserID) + if err != nil { + m := fmt.Sprintf("Failed to get user, error:%v; Failed to redeliver payload for webhook for event:<%s>, user:<%s>, org:<%s>", err.Error(), webhookDelivery.WebhookEventType, webhookDelivery.UserID, organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + go wh.PushWebhookEventToKafkaTopic(webhookDelivery.WebhookEventType, webhookPayloadBytes, wh.WebhooksConfiguration.KafkaConfig.Topic) + + // Log webhook calls in webhooks category + aLog := fmt.Sprintf("Organization webhook: %v triggered by user: %v by event: %v", webhook.PayloadURL, u.Email, webhookDelivery.WebhookEventType) + actionlog.LogOrgWebhookCalls(u.ID.Hex(), u.Email, organizationID, aLog) + + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + +} diff --git a/src/handlerv2/registeruser_handler.go b/src/handlerv2/registeruser_handler.go new file mode 100644 index 0000000..2e8c6a4 --- /dev/null +++ b/src/handlerv2/registeruser_handler.go @@ -0,0 +1,267 @@ +package handlerv2 + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + + "github.com/asaskevich/govalidator" + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/email" + "github.com/bb-consent/api/src/user" +) + +type registerReq struct { + Username string `valid:"required,email"` //Username is email + Name string + Password string `valid:"required,length(8|16)"` + Phone string `valid:"required"` + IsAdmin bool + HlcRegister bool +} + +type iamCredentials struct { + Type string `json:"type"` + Value string `json:"value"` +} + +type iamUserRegisterReq struct { + Username string `json:"username"` + Firstname string `json:"firstName"` + Lastname string `json:"lastName"` + Email string `json:"email"` + Enabled bool `json:"enabled"` + RealmRoles []string `json:"realmRoles"` + Credentials []iamCredentials `json:"credentials"` +} + +// registerUser Registers a new user +func registerUser(iamRegReq iamUserRegisterReq, adminToken string) (int, iamError, error) { + var e iamError + var status = http.StatusInternalServerError + jsonReq, _ := json.Marshal(iamRegReq) + req, err := http.NewRequest("POST", iamConfig.URL+"/admin/realms/"+iamConfig.Realm+"/users", bytes.NewBuffer(jsonReq)) + if err != nil { + return status, e, err + } + + req.Header.Add("Authorization", "Bearer "+adminToken) + req.Header.Add(config.ContentTypeHeader, config.ContentTypeJSON) + + //dump, err := httputil.DumpRequest(req, true) + //dump, err := httputil.DumpRequestOut(req, true) + //log.Printf("\n %q \n", dump) + + client := http.Client{ + Timeout: timeout, + } + resp, err := client.Do(req) + if err != nil { + return status, e, err + } + + defer resp.Body.Close() + if resp.StatusCode != http.StatusCreated { + body, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + + type errorMsg struct { + ErrorMessage string `json:"errorMessage"` + } + var errMsg errorMsg + json.Unmarshal(body, &errMsg) + e.Error = errMsg.ErrorMessage + e.ErrorType = "Creation failed" + return resp.StatusCode, e, errors.New("failed to register user") + } + return resp.StatusCode, e, err +} + +func handleError(w http.ResponseWriter, userName string, status int, iamErr iamError, err error) { + if (iamError{}) != iamErr { + log.Printf("Failed to register err:%v", err) + resp, _ := json.Marshal(iamErr) + w.WriteHeader(status) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.Write(resp) + return + } + m := fmt.Sprintf("Failed to register user:%v", userName) + common.HandleError(w, status, m, err) + return +} + +func getUserIamID(userName string, adminToken string) (string, int, iamError, error) { + var userID = "" + var status = http.StatusInternalServerError + var e iamError + req, err := http.NewRequest("GET", iamConfig.URL+"/admin/realms/"+iamConfig.Realm+"/users"+"?username="+userName, nil) + if err != nil { + return userID, status, e, err + } + //log.Printf("token: %v", t) + req.Header.Add("Authorization", "Bearer "+adminToken) + req.Header.Add(config.ContentTypeHeader, config.ContentTypeJSON) + + //dump, err := httputil.DumpRequest(req, true) + //dump, err := httputil.DumpRequestOut(req, true) + //log.Printf("\n %q \n", dump) + + client := http.Client{ + Timeout: timeout, + } + resp, err := client.Do(req) + if err != nil { + return userID, status, e, err + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + json.Unmarshal(body, &e) + return userID, resp.StatusCode, e, errors.New("failed to register user") + } + type userDetails struct { + ID string `json:"id"` + } + var users []userDetails + json.Unmarshal(body, &users) + return users[0].ID, resp.StatusCode, e, err +} + +type rReq struct { + ClientRole bool `json:"clientRole"` + Composite bool `json:"composite"` + ContainerID string `json:"containerId"` + Description string `json:"description"` + ID string `json:"id"` + Name string `json:"name"` + ScopeParamRequired bool `json:"scopeParamRequired"` +} + +// TODO: Get this from the IAM endpoint +var realmRoleOrgAdmin = "70698dc8-3202-4668-a982-4d95107399d4" + +func setAdminRole(userID string, adminToken string) (int, iamError, error) { + var status = http.StatusInternalServerError + var e iamError + + var iReq []rReq + iReq = append(iReq, rReq{false, false, iamConfig.Realm, "${organization.admin}", realmRoleOrgAdmin, "organization-admin", false}) + jsonReq, _ := json.Marshal(iReq) + req, err := http.NewRequest("POST", iamConfig.URL+"/admin/realms/"+iamConfig.Realm+"/users/"+userID+"/role-mappings/realm", bytes.NewBuffer(jsonReq)) + if err != nil { + return status, e, err + } + + req.Header.Add("Authorization", "Bearer "+adminToken) + req.Header.Add(config.ContentTypeHeader, config.ContentTypeJSON) + + //dump, err := httputil.DumpRequest(req, true) + //dump, err := httputil.DumpRequestOut(req, true) + //fmt.Printf("\n %q \n", dump) + + client := http.Client{ + Timeout: timeout, + } + resp, err := client.Do(req) + if err != nil { + log.Printf("Failed to set user roles: with status:%v", resp.StatusCode) + return status, e, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + + json.Unmarshal(body, &e) + return resp.StatusCode, e, err +} + +// RegisterUser Registers a new user +func RegisterUser(w http.ResponseWriter, r *http.Request) { + var regReq registerReq + b, _ := ioutil.ReadAll(r.Body) + defer r.Body.Close() + + json.Unmarshal(b, ®Req) + + valid, err := govalidator.ValidateStruct(regReq) + if !valid { + handleError(w, regReq.Username, http.StatusBadRequest, iamError{"Invalid user input", err.Error()}, err) + return + } + + var iamRegReq iamUserRegisterReq + iamRegReq.Username = regReq.Username + iamRegReq.Firstname = regReq.Name + //iamRegReq.Lastname = regReq.Lastname + iamRegReq.Email = regReq.Username + iamRegReq.Enabled = true + + iamRegReq.Credentials = append(iamRegReq.Credentials, iamCredentials{"password", regReq.Password}) + iamRegReq.RealmRoles = append(iamRegReq.RealmRoles, "organization-admin") + + t, status, iamErr, err := getAdminToken() + if err != nil { + log.Printf("Failed to get admin token, user: %v registration", regReq.Username) + handleError(w, regReq.Username, status, iamErr, err) + return + } + + status, iamErr, err = registerUser(iamRegReq, t.AccessToken) + if err != nil { + log.Printf("Failed to register user: %v err: %v", regReq.Username, err) + handleError(w, regReq.Username, status, iamErr, err) + return + } + userIamID, status, iamErr, err := getUserIamID(regReq.Username, t.AccessToken) + if err != nil { + log.Printf("Failed to get userID for user: %v err: %v", regReq.Username, err) + handleError(w, regReq.Username, status, iamErr, err) + return + } + if regReq.IsAdmin { + status, iamErr, err = setAdminRole(userIamID, t.AccessToken) + if err != nil { + log.Printf("Failed to set roles for user: %v iam id: %v err: %v", regReq.Username, userIamID, err) + handleError(w, regReq.Username, status, iamErr, err) + return + } + } + var u user.User + u.Name = regReq.Name + u.IamID = userIamID + u.Email = regReq.Username + u.Phone = regReq.Phone + u.Orgs = []user.Org{} + u.Roles = []user.Role{} + + u, err = user.Add(u) + if err != nil { + log.Printf("Failed to add user: %v id: %v to Db err: %v", regReq.Username, userIamID, err) + m := fmt.Sprintf("Failed to register user: %v", regReq.Username) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + // Sending welcome email + go email.SendWelcomeEmail(u.Email, u.Name, "Welcome to iGrant.io", "", email.SMTPConfig.AdminEmail) + + log.Printf("successfully registered user: %v", regReq.Username) + //TODO; json response needed for the creation successful. + type createResponse struct { + Msg string `json:"msg"` + } + resp := createResponse{"User created successfully"} + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusCreated) + w.Write(response) +} diff --git a/src/handlerv2/updatedataagreement_handler.go b/src/handlerv2/updatedataagreement_handler.go new file mode 100644 index 0000000..8f63036 --- /dev/null +++ b/src/handlerv2/updatedataagreement_handler.go @@ -0,0 +1,89 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + "github.com/gorilla/mux" +) + +type organization struct { + Organization org.Organization +} + +// UpdateDataAgreementD Update the given data agreement by ID +func UpdateDataAgreement(w http.ResponseWriter, r *http.Request) { + organizationID := r.Header.Get(config.OrganizationId) + purposeID := mux.Vars(r)[config.DataAgreementId] + + var uReq purpose + b, _ := ioutil.ReadAll(r.Body) + defer r.Body.Close() + + json.Unmarshal(b, &uReq) + + o, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to fetch organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + // Proceed if lawful basis of processing provided is valid + if !isValidLawfulBasisOfProcessing(uReq.LawfulBasisOfProcessing) { + m := fmt.Sprintf("Invalid lawful basis of processing provided") + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + tempLawfulUsage := getLawfulUsageByLawfulBasis(uReq.LawfulBasisOfProcessing) + + var found = false + for i := range o.Purposes { + if o.Purposes[i].ID == purposeID { + found = true + o.Purposes[i].Name = strings.TrimSpace(uReq.Name) + o.Purposes[i].Description = strings.TrimSpace(uReq.Description) + o.Purposes[i].PolicyURL = strings.TrimSpace(uReq.PolicyURL) + o.Purposes[i].LawfulUsage = tempLawfulUsage + o.Purposes[i].LawfulBasisOfProcessing = uReq.LawfulBasisOfProcessing + o.Purposes[i].Jurisdiction = uReq.Jurisdiction + o.Purposes[i].Disclosure = uReq.Disclosure + o.Purposes[i].IndustryScope = uReq.IndustryScope + o.Purposes[i].DataRetention = uReq.DataRetention + o.Purposes[i].Restriction = uReq.Restriction + o.Purposes[i].Shared3PP = uReq.Shared3PP + if (o.Purposes[i].AttributeType != uReq.AttributeType) || + (o.Purposes[i].SSIID != uReq.SSIID) { + m := fmt.Sprintf("Can not modify attributeType or SSIID for purpose: %v organization: %v", + organizationID, purposeID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + } + } + + if !found { + m := fmt.Sprintf("Failed to find purpose with ID: %v in organization: %v", purposeID, o.Name) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + o, err = org.Update(o) + if err != nil { + m := fmt.Sprintf("Failed to update purpose: %v in organization: %v", purposeID, o.Name) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + response, _ := json.Marshal(organization{o}) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusCreated) + w.Write(response) +} diff --git a/src/handlerv2/updatedataattributebyid_handler.go b/src/handlerv2/updatedataattributebyid_handler.go new file mode 100644 index 0000000..7ed71ac --- /dev/null +++ b/src/handlerv2/updatedataattributebyid_handler.go @@ -0,0 +1,82 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + "github.com/gorilla/mux" +) + +// UpdateDataAttributeById Updates an organization data attribute +func UpdateDataAttributeById(w http.ResponseWriter, r *http.Request) { + organizationID := r.Header.Get(config.OrganizationId) + templateID := mux.Vars(r)[config.DataAttributeId] + + var uReq template + b, _ := ioutil.ReadAll(r.Body) + defer r.Body.Close() + + json.Unmarshal(b, &uReq) + + o, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to fetch organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + // validating PurposeIDs provided + if uReq.PurposeIDs != nil { + + for _, p := range uReq.PurposeIDs { + _, err := org.GetPurpose(organizationID, p) + if err != nil { + m := fmt.Sprintf("Invalid purposeID:%v provided;Failed to update template to organization: %v", p, o.Name) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + } + } + + var found = false + + for i := range o.Templates { + if o.Templates[i].ID == templateID { + found = true + + // for partial updation + if strings.TrimSpace(uReq.Consent) != "" { + o.Templates[i].Consent = uReq.Consent + } + // only updating if any valid purpose id was given + if uReq.PurposeIDs != nil { + o.Templates[i].PurposeIDs = uReq.PurposeIDs + } + } + } + + if !found { + m := fmt.Sprintf("Failed to find template with ID: %v in organization: %v", templateID, o.Name) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + o, err = org.Update(o) + if err != nil { + m := fmt.Sprintf("Failed to update template: %v in organization: %v", templateID, o.Name) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + response, _ := json.Marshal(organization{o}) + + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusCreated) + w.Write(response) +} diff --git a/src/handlerv2/updateglobalpolicyconfigurationbyid_handler.go b/src/handlerv2/updateglobalpolicyconfigurationbyid_handler.go new file mode 100644 index 0000000..1e6cbb5 --- /dev/null +++ b/src/handlerv2/updateglobalpolicyconfigurationbyid_handler.go @@ -0,0 +1,21 @@ +package handlerv2 + +import ( + "encoding/json" + "net/http" + + "github.com/bb-consent/api/src/config" +) + +// UpdateGlobalPolicyConfigurationById Handler to update global policy configuration +func UpdateGlobalPolicyConfigurationById(w http.ResponseWriter, r *http.Request) { + + // Constructing the response + var resp globalPolicyConfigurationResp + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) + +} diff --git a/src/handlerv2/updateglobalpolicyconfigurations_handler.go b/src/handlerv2/updateglobalpolicyconfigurations_handler.go new file mode 100644 index 0000000..a27ed1d --- /dev/null +++ b/src/handlerv2/updateglobalpolicyconfigurations_handler.go @@ -0,0 +1,105 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + "github.com/bb-consent/api/src/orgtype" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type globalPolicyConfigurationReq struct { + PolicyURL string + RetentionPeriod int + Jurisdiction string + Disclosure string + TypeID string `valid:"required"` + Restriction string + Shared3PP bool +} + +// UpdateGlobalPolicyConfiguration Handler to update global policy configuration +func UpdateGlobalPolicyConfiguration(w http.ResponseWriter, r *http.Request) { + organizationID := r.Header.Get(config.OrganizationId) + + o, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to fetch organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + var policyReq globalPolicyConfigurationReq + b, _ := ioutil.ReadAll(r.Body) + defer r.Body.Close() + + json.Unmarshal(b, &policyReq) + + // Update global policy configuration for the org + o.PolicyURL = policyReq.PolicyURL + + if len(strings.TrimSpace(policyReq.Jurisdiction)) != 0 { + o.Jurisdiction = policyReq.Jurisdiction + } + + o.Restriction = policyReq.Restriction + o.Shared3PP = policyReq.Shared3PP + + if policyReq.Disclosure == "false" || policyReq.Disclosure == "true" { + o.Disclosure = policyReq.Disclosure + } + + // Check if type id is valid bson objectid hex + if !primitive.IsValidObjectID(policyReq.TypeID) { + m := fmt.Sprintf("Invalid organization type ID: %v", policyReq.TypeID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + orgType, err := orgtype.Get(policyReq.TypeID) + if err != nil { + m := fmt.Sprintf("Invalid organization type ID: %v", policyReq.TypeID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + o.Type = orgType + + if policyReq.RetentionPeriod > 0 { + o.DataRetention.RetentionPeriod = int64(policyReq.RetentionPeriod) + o.DataRetention.Enabled = true + } else { + o.DataRetention.RetentionPeriod = 0 + o.DataRetention.Enabled = false + } + + // Updating global configuration policy with defaults + o, err = org.Update(o) + if err != nil { + m := fmt.Sprintf("Failed to update global configuration to organization: %v", organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + // Constructing the response + var resp globalPolicyConfigurationResp + resp.PolicyURL = o.PolicyURL + resp.DataRetention = o.DataRetention + resp.Jurisdiction = o.Jurisdiction + resp.Disclosure = o.Disclosure + resp.Type = o.Type + resp.Restriction = o.Restriction + resp.Shared3PP = o.Shared3PP + + response, _ := json.Marshal(resp) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) + +} diff --git a/src/handlerv2/updateidentityprovider_handler.go b/src/handlerv2/updateidentityprovider_handler.go new file mode 100644 index 0000000..99073b7 --- /dev/null +++ b/src/handlerv2/updateidentityprovider_handler.go @@ -0,0 +1,279 @@ +package handlerv2 + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + "strings" + + "github.com/asaskevich/govalidator" + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" +) + +// Helper function to update identity provider to iGrant.io IAM +func updateIdentityProvider(identityProviderAlias string, identityProviderRepresentation org.IdentityProviderRepresentation, adminToken string) (int, iamError, error) { + var e iamError + var status = http.StatusInternalServerError + jsonReq, _ := json.Marshal(identityProviderRepresentation) + req, err := http.NewRequest("PUT", iamConfig.URL+"/admin/realms/"+iamConfig.Realm+"/identity-provider/instances/"+identityProviderAlias, bytes.NewBuffer(jsonReq)) + if err != nil { + return status, e, err + } + + req.Header.Add("Authorization", "Bearer "+adminToken) + req.Header.Add(config.ContentTypeHeader, config.ContentTypeJSON) + + client := http.Client{ + Timeout: timeout, + } + resp, err := client.Do(req) + if err != nil { + return status, e, err + } + + if resp.StatusCode != http.StatusNoContent { + body, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + + type errorMsg struct { + ErrorMessage string `json:"errorMessage"` + } + var errMsg errorMsg + json.Unmarshal(body, &errMsg) + e.Error = errMsg.ErrorMessage + e.ErrorType = "Identity provider update failed" + return resp.StatusCode, e, errors.New("failed to update identity provider") + } + + defer resp.Body.Close() + + return resp.StatusCode, e, err +} + +func getClientsInRealm(clientID string, adminToken string) (string, int, iamError, error) { + var e iamError + var status = http.StatusInternalServerError + + req, err := http.NewRequest("GET", iamConfig.URL+"/admin/realms/"+iamConfig.Realm+"/clients?clientId="+clientID, nil) + if err != nil { + return "", status, e, err + } + + req.Header.Add("Authorization", "Bearer "+adminToken) + req.Header.Add(config.ContentTypeHeader, config.ContentTypeJSON) + + client := http.Client{ + Timeout: timeout, + } + resp, err := client.Do(req) + if err != nil { + return "", status, e, err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + + type errorMsg struct { + ErrorMessage string `json:"errorMessage"` + } + var errMsg errorMsg + json.Unmarshal(body, &errMsg) + + e.Error = errMsg.ErrorMessage + e.ErrorType = "OpenID client secret generation failed" + return "", resp.StatusCode, e, errors.New("failed to generate secret for OpenID client") + } + + body, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + var responseBody []map[string]interface{} + json.Unmarshal(body, &responseBody) + + defer resp.Body.Close() + + return responseBody[0]["id"].(string), resp.StatusCode, e, err + +} + +// Helper function to update OpenID client to manage login sessions for the external identity provider +func updateOpenIDClient(clientUUID string, keycloakOpenIDClient org.KeycloakOpenIDClient, adminToken string) (int, iamError, error) { + + var e iamError + var status = http.StatusInternalServerError + jsonReq, _ := json.Marshal(keycloakOpenIDClient) + req, err := http.NewRequest("PUT", iamConfig.URL+"/admin/realms/"+iamConfig.Realm+"/clients/"+clientUUID, bytes.NewBuffer(jsonReq)) + if err != nil { + return status, e, err + } + + req.Header.Add("Authorization", "Bearer "+adminToken) + req.Header.Add(config.ContentTypeHeader, config.ContentTypeJSON) + + client := http.Client{ + Timeout: timeout, + } + resp, err := client.Do(req) + if err != nil { + return status, e, err + } + + if resp.StatusCode != http.StatusNoContent { + body, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + + type errorMsg struct { + ErrorMessage string `json:"errorMessage"` + } + var errMsg errorMsg + json.Unmarshal(body, &errMsg) + e.Error = errMsg.ErrorMessage + e.ErrorType = "OpenID client update failed" + return resp.StatusCode, e, errors.New("failed to update OpenID client") + } + + body, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + var responseBody interface{} + json.Unmarshal(body, &responseBody) + + return resp.StatusCode, e, err + +} + +// UpdateIdentityProvider Update external identity provider for an organisation +func UpdateIdentityProvider(w http.ResponseWriter, r *http.Request) { + + // Get the org ID and fetch the organization from the db. + organizationID := r.Header.Get(config.OrganizationId) + o, err := org.Get(organizationID) + + if err != nil { + m := fmt.Sprintf("Failed to fetch org; Failed to update identity provider for %v", organizationID) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + if !o.ExternalIdentityProviderAvailable { + m := fmt.Sprintf("External IDP provider doesn't exist; Try to create instead of update; Failed to create identity provider for %v", organizationID) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + // Deserializing the request payload to struct + b, _ := ioutil.ReadAll(r.Body) + defer r.Body.Close() + + var updateIdentityProviderReq IdentityProviderReq + json.Unmarshal(b, &updateIdentityProviderReq) + + // validating request payload + valid, err := govalidator.ValidateStruct(updateIdentityProviderReq) + if !valid { + m := fmt.Sprintf("Missing mandatory params for updating identity provider for org:%v\n", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + var identityProviderOpenIDConfig org.IdentityProviderOpenIDConfig + + // Update OpenID config + identityProviderOpenIDConfig.AuthorizationURL = updateIdentityProviderReq.AuthorizationURL + identityProviderOpenIDConfig.TokenURL = updateIdentityProviderReq.TokenURL + identityProviderOpenIDConfig.LogoutURL = updateIdentityProviderReq.LogoutURL + identityProviderOpenIDConfig.ClientID = updateIdentityProviderReq.ClientID + identityProviderOpenIDConfig.ClientSecret = updateIdentityProviderReq.ClientSecret + identityProviderOpenIDConfig.JWKSURL = updateIdentityProviderReq.JWKSURL + identityProviderOpenIDConfig.UserInfoURL = updateIdentityProviderReq.UserInfoURL + identityProviderOpenIDConfig.ValidateSignature = updateIdentityProviderReq.ValidateSignature + identityProviderOpenIDConfig.DefaultScope = updateIdentityProviderReq.DefaultScope + + if len(strings.TrimSpace(updateIdentityProviderReq.LogoutURL)) > 0 { + identityProviderOpenIDConfig.BackchannelSupported = true + } else { + identityProviderOpenIDConfig.BackchannelSupported = false + } + + identityProviderOpenIDConfig.DisableUserInfo = updateIdentityProviderReq.DisableUserInfo + identityProviderOpenIDConfig.Issuer = updateIdentityProviderReq.Issuer + + if len(strings.TrimSpace(updateIdentityProviderReq.JWKSURL)) > 0 { + identityProviderOpenIDConfig.UseJWKSURL = true + } else { + identityProviderOpenIDConfig.UseJWKSURL = false + } + + identityProviderOpenIDConfig.SyncMode = "IMPORT" + identityProviderOpenIDConfig.ClientAuthMethod = "client_secret_post" + identityProviderOpenIDConfig.HideOnLoginPage = true + + // Fetch admin token from keycloak + t, status, _, err := getAdminToken() + if err != nil { + m := fmt.Sprintf("Failed to fetch the admin token from keycloak; Failed to update identity provider for %v", organizationID) + common.HandleError(w, status, m, err) + return + } + + // Updating identity provider OpenID config + o.IdentityProviderRepresentation.Config = identityProviderOpenIDConfig + + // Update identity provider in iGrant.io IAM + status, _, err = updateIdentityProvider(o.IdentityProviderRepresentation.Alias, o.IdentityProviderRepresentation, t.AccessToken) + if err != nil { + m := fmt.Sprintf("Failed to create external identity provider in keycloak; Failed to update identity provider for %v", organizationID) + common.HandleError(w, status, m, err) + return + } + + // Update the identity provider details to the database + o, err = org.UpdateIdentityProviderByOrgID(organizationID, o.IdentityProviderRepresentation) + if err != nil { + m := fmt.Sprintf("Failed to update IDP config to database; Failed to update identity provider for %v", organizationID) + common.HandleError(w, status, m, err) + return + } + + // FIX ME : Is this right practice to do it anonymous function executed in a separate thread ? + go func() { + // Update the OpenID client + o.KeycloakOpenIDClient.Attributes.BackchannelLogoutURL = updateIdentityProviderReq.LogoutURL + + // Fetch OpenID client UUID + openIDClientUUID, _, _, err := getClientsInRealm(o.KeycloakOpenIDClient.ClientID, t.AccessToken) + if err != nil { + m := fmt.Sprintf("Failed to fetch OpenID client UUID from keycloak; Failed to update identity provider for %v", organizationID) + common.HandleError(w, http.StatusNotFound, m, err) + return + } + + // Update OpenID client to iGrant.io IAM + status, _, err = updateOpenIDClient(openIDClientUUID, o.KeycloakOpenIDClient, t.AccessToken) + if err != nil { + m := fmt.Sprintf("Failed to udpate OpenID client in keycloak; Failed to update identity provider for %v", organizationID) + log.Println(m) + return + } + + // Update the OpenID client details to the database + o, err = org.UpdateOpenIDClientByOrgID(organizationID, o.KeycloakOpenIDClient) + if err != nil { + m := fmt.Sprintf("Failed to update OpenID client config to database; Failed to update identity provider for %v", organizationID) + log.Println(m) + return + } + }() + + response, _ := json.Marshal(o.IdentityProviderRepresentation.Config) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) +} diff --git a/src/handlerv2/updateuser_handler.go b/src/handlerv2/updateuser_handler.go new file mode 100644 index 0000000..49401f7 --- /dev/null +++ b/src/handlerv2/updateuser_handler.go @@ -0,0 +1,14 @@ +package handlerv2 + +import ( + "net/http" + + "github.com/bb-consent/api/src/config" +) + +func UpdateUser(w http.ResponseWriter, r *http.Request) { + + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + +} diff --git a/src/handlerv2/updatewebhook_handler.go b/src/handlerv2/updatewebhook_handler.go new file mode 100644 index 0000000..cf5ddeb --- /dev/null +++ b/src/handlerv2/updatewebhook_handler.go @@ -0,0 +1,156 @@ +package handlerv2 + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "strings" + + "github.com/asaskevich/govalidator" + "github.com/bb-consent/api/src/common" + "github.com/bb-consent/api/src/config" + "github.com/bb-consent/api/src/org" + wh "github.com/bb-consent/api/src/webhooks" + "github.com/gorilla/mux" +) + +// updateWebhookReq Defines the request payload structure for updating a webhook for an organisation +type updateWebhookReq struct { + PayloadURL string `valid:"required,url"` // Webhook endpoint URL + SubscribedEvents []string `valid:"required"` // Events subscribed for e.g. user.data.delete + ContentType string `valid:"required"` // Data format for the webhook payload + Disabled bool // Disabled or not + SecretKey string // For calculating SHA256 HMAC to verify data integrity and authenticity + SkipSSLVerification bool // Skip SSL certificate verification or not (expiry is checked) +} + +// UpdateWebhook Updates a webhook for an organisation by ID +func UpdateWebhook(w http.ResponseWriter, r *http.Request) { + // Reading the URL parameters + organizationID := r.Header.Get(config.OrganizationId) + webhookID := mux.Vars(r)[config.WebhookId] + + fmt.Printf("Mux vars : %v\n", mux.Vars(r)) + + // Validating the given organisation ID + _, err := org.Get(organizationID) + if err != nil { + m := fmt.Sprintf("Failed to get organization: %v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + sanitizedOrgId := common.Sanitize(organizationID) + sanitizedWebhookId := common.Sanitize(webhookID) + + // Validating the given webhook ID for an organisation + webhook, err := wh.GetByOrgID(sanitizedWebhookId, sanitizedOrgId) + if err != nil { + m := fmt.Sprintf("Failed to get webhook:%v for organisation: %v", webhookID, organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + var requestPayload updateWebhookReq + + // Reading request body as bytes and unmarshalling to a struct + b, _ := ioutil.ReadAll(r.Body) + defer r.Body.Close() + json.Unmarshal(b, &requestPayload) + + // Validating request payload struct + valid, err := govalidator.ValidateStruct(requestPayload) + if valid != true { + log.Printf("Missing mandatory params; Failed to update webhook for organisation: %v", organizationID) + common.HandleError(w, http.StatusBadRequest, err.Error(), err) + return + } + + // Check if the webhook endpoint contains http:// or https:// + if !(strings.HasPrefix(requestPayload.PayloadURL, "https://") || strings.HasPrefix(requestPayload.PayloadURL, "http://")) { + m := fmt.Sprintf("Please prefix the endpoint URL with https:// or http://; Failed to update webhook for organisation:%v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + sanitizedPayloadURL := common.Sanitize(requestPayload.PayloadURL) + + // Check if webhook with provided payload URL already exists + tempWebhook, err := wh.GetWebhookByPayloadURL(sanitizedOrgId, sanitizedPayloadURL) + if err == nil { + if tempWebhook.ID.Hex() != webhookID { + m := fmt.Sprintf("Webhook with provided payload URL already exists; Failed to update webhook for organisation:%v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + } + + // Check if subscribed events array is empty + if len(requestPayload.SubscribedEvents) == 0 { + m := fmt.Sprintf("Provide atleast 1 event type in subscribed events; Failed to update webhook for organisation:%v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Check if subscribed event type(s) contains duplicates + requestPayload.SubscribedEvents = uniqueSlice(requestPayload.SubscribedEvents) + + // Check the subscribed event type ID(s) provided is valid + var isValidSubscribedEvents bool + for _, subscribedEventType := range requestPayload.SubscribedEvents { + isValidSubscribedEvents = false + for _, eventType := range wh.EventTypes { + if subscribedEventType == eventType { + isValidSubscribedEvents = true + break + } + } + + if !isValidSubscribedEvents { + break + } + } + + if !isValidSubscribedEvents { + m := fmt.Sprintf("Invalid event type provided in subscribed events; Failed to update webhook for organisation:%v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Check if the content type ID provided is valid + isValidContentType := false + for _, payloadContentType := range wh.PayloadContentTypes { + if requestPayload.ContentType == payloadContentType { + isValidContentType = true + } + } + + if !isValidContentType { + m := fmt.Sprintf("Invalid content type provided; Failed to update webhook for organisation:%v", organizationID) + common.HandleError(w, http.StatusBadRequest, m, err) + return + } + + // Updating webhook + webhook.PayloadURL = strings.TrimSpace(requestPayload.PayloadURL) + webhook.ContentType = requestPayload.ContentType + webhook.SubscribedEvents = requestPayload.SubscribedEvents + webhook.Disabled = requestPayload.Disabled + webhook.SecretKey = requestPayload.SecretKey + webhook.SkipSSLVerification = requestPayload.SkipSSLVerification + + webhook, err = wh.UpdateWebhook(webhook) + if err != nil { + m := fmt.Sprintf("Failed to update webhook:%v for organisation: %v", webhookID, organizationID) + common.HandleError(w, http.StatusInternalServerError, m, err) + return + } + + response, _ := json.Marshal(webhook) + w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(response) + +} diff --git a/src/main/audit_paths.go b/src/httppathsv1/audit_paths.go similarity index 72% rename from src/main/audit_paths.go rename to src/httppathsv1/audit_paths.go index 71511cc..29969fe 100644 --- a/src/main/audit_paths.go +++ b/src/httppathsv1/audit_paths.go @@ -1,3 +1,3 @@ -package main +package httppathsv1 const GetOrgLogs = "/v1/organizations/{orgID}/logs" diff --git a/src/main/config_paths.go b/src/httppathsv1/config_paths.go similarity index 99% rename from src/main/config_paths.go rename to src/httppathsv1/config_paths.go index 62758d3..c90ca55 100644 --- a/src/main/config_paths.go +++ b/src/httppathsv1/config_paths.go @@ -1,4 +1,4 @@ -package main +package httppathsv1 // Global policy configuration const GetGlobalPolicyConfiguration = "/v1/organizations/{organizationID}/global-policy-configuration" diff --git a/src/main/onboard_paths.go b/src/httppathsv1/onboard_paths.go similarity index 99% rename from src/main/onboard_paths.go rename to src/httppathsv1/onboard_paths.go index 2b9efb6..f3779e0 100644 --- a/src/main/onboard_paths.go +++ b/src/httppathsv1/onboard_paths.go @@ -1,4 +1,4 @@ -package main +package httppathsv1 const AddOrganization = "/v1/organizations" const GetOrganizationRoles = "/v1/organizations/roles" diff --git a/src/main/routes.go b/src/httppathsv1/routes.go similarity index 99% rename from src/main/routes.go rename to src/httppathsv1/routes.go index 8b15797..4898dde 100644 --- a/src/main/routes.go +++ b/src/httppathsv1/routes.go @@ -1,9 +1,9 @@ -package main +package httppathsv1 import ( "net/http" - "github.com/bb-consent/api/src/handler" + handler "github.com/bb-consent/api/src/handlerv1" m "github.com/bb-consent/api/src/middleware" "github.com/casbin/casbin/v2" "github.com/gorilla/mux" diff --git a/src/main/service_paths.go b/src/httppathsv1/service_paths.go similarity index 98% rename from src/main/service_paths.go rename to src/httppathsv1/service_paths.go index be9977f..8a008f3 100644 --- a/src/main/service_paths.go +++ b/src/httppathsv1/service_paths.go @@ -1,4 +1,4 @@ -package main +package httppathsv1 const GetUserConsentHistory = "/v1/users/{userID}/consenthistory" const GetConsentPurposeByID = "/v1/organizations/{orgID}/users/{userID}/consents/{consentID}/purposes/{purposeID}" diff --git a/src/httppathsv2/audit_paths.go b/src/httppathsv2/audit_paths.go new file mode 100644 index 0000000..4d9bf1a --- /dev/null +++ b/src/httppathsv2/audit_paths.go @@ -0,0 +1 @@ +package httppathsv2 diff --git a/src/httppathsv2/config_paths.go b/src/httppathsv2/config_paths.go new file mode 100644 index 0000000..1cf5760 --- /dev/null +++ b/src/httppathsv2/config_paths.go @@ -0,0 +1,54 @@ +package httppathsv2 + +// Global policy configuration +const UpdateGlobalPolicyConfigurations = "/v2/config/policy" +const GetGlobalPolicyConfigurations = "/v2/config/policy" +const UpdateGlobalPolicyConfigurationById = "/v2/config/policy/{policyId}/" +const OrgListPolicyRevisions = "/v2/config/policy/{policyId}/revisions/" +const OrgDeletePolicy = "/v2/config/policy/{policyId}/" +const OrgListPolicy = "/v2/config/policies/" + +// Data agreements +const GetDataAgreementById = "/v2/config/data-agreement/{dataAgreementId}" +const AddDataAgreement = "/v2/config/data-agreement/" +const UpdateDataAgreement = "/v2/config/data-agreement/{dataAgreementId}" +const DeleteDataAgreement = "/v2/config/data-agreement/{dataAgreementId}" +const ListDataAgreements = "/v2/config/data-agreements" +const ListDataAgreementRevisions = "/v2/config/data-agreement/{dataAgreementId}/revisions" +const ReadDataAgreementRevision = "/v2/config/data-agreement/{dataAgreementId}/revision/{revisionId}" + +// Data attributes +const GetDataAttributes = "/v2/config/data-agreements/data-attributes" +const AddDataAttribute = "/v2/config/data-agreements/data-attribute" +const UpdateDataAttributeById = "/v2/config/data-agreements/data-attribute/{dataAttributeId}" +const DeleteDataAttributeById = "/v2/config/data-agreements/data-attribute/{dataAttributeId}" + +// Webhooks +const GetWebhookEventTypes = "/v2/config/webhooks/event-types" +const GetWebhookPayloadContentTypes = "/v2/config/webhooks/payload/content-types" +const GetAllWebhooks = "/v2/config/webhooks" +const CreateWebhook = "/v2/config/webhook" +const UpdateWebhook = "/v2/config/webhook/{webhookId}" +const DeleteWebhook = "/v2/config/webhook/{webhookId}" +const PingWebhook = "/v2/config/webhook/{webhookId}/ping" +const GetRecentWebhookDeliveries = "/v2/config/webhooks/{webhookId}/delivery" +const GetRecentWebhookDeliveryById = "/v2/config/webhooks/{webhookId}/delivery/{deliveryId}" +const RedeliverWebhookPayloadByDeliveryID = "/v2/config/webhooks/{webhookId}/delivery/{deliveryId}/redeliver" + +// Organisation identity provider related API(s) +const AddIdentityProvider = "/v2/config/idp/open-id" +const UpdateIdentityProvider = "/v2/config/idp/open-id" +const DeleteIdentityProvider = "/v2/config/idp/open-id" +const GetIdentityProvider = "/v2/config/idp/open-id" + +// Individuals +const GetOrganizationUsers = "/v2/config/individuals" +const RegisterUser = "/v2/config/individual" +const GetUser = "/v2/config/individual/{individualId}" +const DeleteUser = "/v2/config/individual/{individualId}" +const UpdateUser = "/v2/config/individual/{individualId}" + +// Api key +const CreateAPIKey = "/v2/config/admin/apikey" +const DeleteAPIKey = "/v2/config/admin/apikey" +const GetAPIKey = "/v2/config/admin/apikey" diff --git a/src/httppathsv2/onboard_paths.go b/src/httppathsv2/onboard_paths.go new file mode 100644 index 0000000..4d9bf1a --- /dev/null +++ b/src/httppathsv2/onboard_paths.go @@ -0,0 +1 @@ +package httppathsv2 diff --git a/src/httppathsv2/routes.go b/src/httppathsv2/routes.go new file mode 100644 index 0000000..3e13f2c --- /dev/null +++ b/src/httppathsv2/routes.go @@ -0,0 +1,63 @@ +package httppathsv2 + +import ( + handler "github.com/bb-consent/api/src/handlerv2" + m "github.com/bb-consent/api/src/middleware" + "github.com/casbin/casbin/v2" + "github.com/gorilla/mux" +) + +// SetRoutes sets the routes that the back end server serves +func SetRoutes(r *mux.Router, e *casbin.Enforcer) { + // Organization global policy configuration + r.Handle(GetGlobalPolicyConfigurations, m.Chain(handler.GetGlobalPolicyConfiguration, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + r.Handle(UpdateGlobalPolicyConfigurations, m.Chain(handler.UpdateGlobalPolicyConfiguration, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("POST") + r.Handle(UpdateGlobalPolicyConfigurationById, m.Chain(handler.UpdateGlobalPolicyConfigurationById, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("PUT") + r.Handle(OrgListPolicyRevisions, m.Chain(handler.OrgListPolicyRevisions, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + r.Handle(OrgDeletePolicy, m.Chain(handler.OrgDeletePolicy, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("DELETE") + r.Handle(OrgListPolicy, m.Chain(handler.OrgListPolicy, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + + r.Handle(GetDataAgreementById, m.Chain(handler.GetDataAgreementById, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + r.Handle(AddDataAgreement, m.Chain(handler.AddDataAgreement, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("POST") + r.Handle(UpdateDataAgreement, m.Chain(handler.UpdateDataAgreement, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("PUT") + r.Handle(DeleteDataAgreement, m.Chain(handler.DeleteDataAgreement, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("DELETE") + r.Handle(ListDataAgreements, m.Chain(handler.ListDataAgreements, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + r.Handle(ListDataAgreementRevisions, m.Chain(handler.ListDataAgreementRevisions, m.Logger(), m.SetApplicationMode(), m.Authorize(e), m.Authenticate())).Methods("GET") + r.Handle(ReadDataAgreementRevision, m.Chain(handler.ReadDataAgreementRevision, m.Logger(), m.SetApplicationMode(), m.Authorize(e), m.Authenticate())).Methods("GET") + + r.Handle(GetDataAttributes, m.Chain(handler.GetDataAttributes, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + r.Handle(AddDataAttribute, m.Chain(handler.AddDataAttribute, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("POST") + r.Handle(UpdateDataAttributeById, m.Chain(handler.UpdateDataAttributeById, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("PUT") + r.Handle(DeleteDataAttributeById, m.Chain(handler.DeleteDataAttributeById, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("DELETE") + + // Organisation webhooks related api(s) + r.Handle(GetWebhookEventTypes, m.Chain(handler.GetWebhookEventTypes, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + r.Handle(GetWebhookPayloadContentTypes, m.Chain(handler.GetWebhookPayloadContentTypes, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + r.Handle(GetAllWebhooks, m.Chain(handler.GetAllWebhooks, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + r.Handle(CreateWebhook, m.Chain(handler.CreateWebhook, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("POST") + r.Handle(UpdateWebhook, m.Chain(handler.UpdateWebhook, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("PUT") + r.Handle(DeleteWebhook, m.Chain(handler.DeleteWebhook, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("DELETE") + r.Handle(PingWebhook, m.Chain(handler.PingWebhook, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("POST") + r.Handle(GetRecentWebhookDeliveries, m.Chain(handler.GetRecentWebhookDeliveries, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + r.Handle(GetRecentWebhookDeliveryById, m.Chain(handler.GetRecentWebhookDeliveryById, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + r.Handle(RedeliverWebhookPayloadByDeliveryID, m.Chain(handler.RedeliverWebhookPayloadByDeliveryID, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("POST") + + // Organisation identity provider related API(s) + r.Handle(AddIdentityProvider, m.Chain(handler.AddIdentityProvider, m.Logger(), m.Authorize(e), m.Authenticate())).Methods("POST") + r.Handle(UpdateIdentityProvider, m.Chain(handler.UpdateIdentityProvider, m.Logger(), m.Authorize(e), m.Authenticate())).Methods("PUT") + r.Handle(DeleteIdentityProvider, m.Chain(handler.DeleteIdentityProvider, m.Logger(), m.Authorize(e), m.Authenticate())).Methods("DELETE") + r.Handle(GetIdentityProvider, m.Chain(handler.GetIdentityProvider, m.Logger(), m.Authorize(e), m.Authenticate())).Methods("GET") + + // Individual related api(s) + r.Handle(GetOrganizationUsers, m.Chain(handler.GetOrganizationUsers, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + r.Handle(RegisterUser, m.Chain(handler.RegisterUser, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("POST") + r.Handle(GetUser, m.Chain(handler.GetUser, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + r.Handle(DeleteUser, m.Chain(handler.DeleteUser, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("DELETE") + r.Handle(UpdateUser, m.Chain(handler.UpdateUser, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("PUT") + + // Api key related api(s) + r.Handle(CreateAPIKey, m.Chain(handler.CreateAPIKey, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("POST") + r.Handle(DeleteAPIKey, m.Chain(handler.DeleteAPIKey, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("DELETE") + r.Handle(GetAPIKey, m.Chain(handler.GetAPIKey, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate())).Methods("GET") + +} diff --git a/src/httppathsv2/service_paths.go b/src/httppathsv2/service_paths.go new file mode 100644 index 0000000..4d9bf1a --- /dev/null +++ b/src/httppathsv2/service_paths.go @@ -0,0 +1 @@ +package httppathsv2 diff --git a/src/main/main.go b/src/main/main.go index a7d11f0..badf86c 100644 --- a/src/main/main.go +++ b/src/main/main.go @@ -11,7 +11,9 @@ import ( "github.com/bb-consent/api/src/database" "github.com/bb-consent/api/src/email" "github.com/bb-consent/api/src/firebaseUtils" - "github.com/bb-consent/api/src/handler" + handler "github.com/bb-consent/api/src/handlerv1" + "github.com/bb-consent/api/src/httppathsv1" + "github.com/bb-consent/api/src/httppathsv2" "github.com/bb-consent/api/src/kafkaUtils" "github.com/bb-consent/api/src/middleware" "github.com/bb-consent/api/src/notifications" @@ -93,7 +95,8 @@ func main() { } router := mux.NewRouter() - SetRoutes(router, authEnforcer) + httppathsv1.SetRoutes(router, authEnforcer) + httppathsv2.SetRoutes(router, authEnforcer) log.Println("Listening port 80") http.ListenAndServe(":80", router) diff --git a/src/middleware/middleware.go b/src/middleware/middleware.go index 8943ec8..a8245e7 100644 --- a/src/middleware/middleware.go +++ b/src/middleware/middleware.go @@ -7,7 +7,7 @@ import ( "time" "github.com/bb-consent/api/src/apikey" - "github.com/bb-consent/api/src/handler" + handler "github.com/bb-consent/api/src/handlerv1" "github.com/bb-consent/api/src/rbac" "github.com/casbin/casbin/v2" "github.com/gorilla/mux" @@ -217,14 +217,14 @@ func SetApplicationMode() Middleware { // Define the http.HandlerFunc return func(w http.ResponseWriter, r *http.Request) { - if ApplicationMode == config.SingleTenat { + if ApplicationMode == config.SingleTenant { organizationId, err := handler.GetOrganizationId() if err != nil { m := "failed to find organization" common.HandleError(w, http.StatusBadRequest, m, err) return } - r.Header.Set("OrganizationID", organizationId) + r.Header.Set(config.OrganizationId, organizationId) } // Call the next middleware/handler in chain diff --git a/src/org/organizations.go b/src/org/organizations.go index 972eb2d..dae6b60 100644 --- a/src/org/organizations.go +++ b/src/org/organizations.go @@ -259,7 +259,7 @@ func Get(organizationID string) (Organization, error) { return result, err } -// Get Gets a single organization +// GetOrganization Gets a single organization func GetOrganization() (Organization, error) { var result Organization