Removed outlook hook
This commit is contained in:
@@ -1,57 +0,0 @@
|
|||||||
# Outlook trigger
|
|
||||||
Makes it possible to trigger a workflow based on an email
|
|
||||||
|
|
||||||
## Local testing - Same as ../webhook
|
|
||||||
```bash
|
|
||||||
mv ../main.go
|
|
||||||
go run main.go hook.go
|
|
||||||
```
|
|
||||||
|
|
||||||
# Deploy gcloud
|
|
||||||
gcloud functions deploy outlooktrigger --runtime go111 --entry-point Authorization --trigger-http --project shuffler --memory=128 --set-env-vars=FUNCTION_APIKEY=asdasd,CALLBACKURL=shuffler.io,TRIGGERID=test123,WORKFLOW_ID=YOUR_WORKFLOW_ID
|
|
||||||
|
|
||||||
# Build and deploy
|
|
||||||
1. Set hook.go line 1 from "package main" to "package function"
|
|
||||||
2. zip outlooktrigger.tar hook.go
|
|
||||||
3. Upload to bucket https://console.cloud.google.com/storage/browser/shuffler.appspot.com?project=shuffler
|
|
||||||
4. Go to the functions https://console.cloud.google.com/functions/list?project=shuffler
|
|
||||||
|
|
||||||
|
|
||||||
## How it works (from frontend to backend)
|
|
||||||
### Choose mailfolders
|
|
||||||
1. Use microsoft graph api to get the folders the user wants to listen to
|
|
||||||
* Have the user write their primary email (default) or another one
|
|
||||||
* Have it show the folders for the email with chooseable buttons somehow
|
|
||||||
|inbox
|
|
||||||
|-subinbox
|
|
||||||
|--subsubinbox <-- choose e.g. this one
|
|
||||||
|otherfolder
|
|
||||||
|
|
||||||
API:
|
|
||||||
// requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/users/me/mailfolders")
|
|
||||||
|
|
||||||
### Add callback subscription
|
|
||||||
2. Make an APIcall to ("https://outlook.office.com/api/v2.0/me/mailfolders('inbox')/messages") with callback url defined as "https://shuffler.io/api/v1/workflows/{key}/email/authorize"
|
|
||||||
* Should this be set up whenever the user clicked start or when the workflow is created?
|
|
||||||
* Start click ->
|
|
||||||
1. Add another cloud function for the item
|
|
||||||
2. When it's ready, deploy it to authorize
|
|
||||||
3. Show it as ready
|
|
||||||
|
|
||||||
### Remove a subscription
|
|
||||||
* Since everything is already generated above, one would need to
|
|
||||||
* https://docs.microsoft.com/en-us/graph/api/subscription-delete?view=graph-rest-1.0&tabs=http
|
|
||||||
* DELETE https://graph.microsoft.com/v1.0/subscriptions/{id}
|
|
||||||
|
|
||||||
|
|
||||||
## CREATE - Fixme: LIST all current subscriptions, and stop them if they're towards the same endpoint
|
|
||||||
* POST /api/v1/workflows/{key}/outlook
|
|
||||||
* createOutlookSub(resp, request)
|
|
||||||
* getOutlookSubscriptions(client) // Used to remove all existing for same endpoint
|
|
||||||
* makeOutlookSubscription(client, folderIds, notificationUrl)
|
|
||||||
* Add data from ^ to triggerAuth
|
|
||||||
|
|
||||||
## DELETE
|
|
||||||
* DELETE /api/v1/workflows/{key}/outlook/{triggerId}
|
|
||||||
* handleDeleteOutlookSub(resp, request)
|
|
||||||
* handleOutlookSubRemoval(workflowId, triggerId)
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
package function
|
|
||||||
|
|
||||||
// Shuffle:
|
|
||||||
// https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Authentication/appId/e080cbf4-5dba-44b4-8643-a7c982189c16/isMSAApp//defaultBlade/Overview/servicePrincipalCreated/true
|
|
||||||
|
|
||||||
// Oauth playground:
|
|
||||||
// https://oauthplay.azurewebsites.net/
|
|
||||||
|
|
||||||
// APPS:
|
|
||||||
// https://apps.dev.microsoft.com
|
|
||||||
|
|
||||||
// REMOVE ACCESS:
|
|
||||||
// https://portal.office.com/account/#
|
|
||||||
|
|
||||||
// Developer:
|
|
||||||
// https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference
|
|
||||||
|
|
||||||
// Bots:
|
|
||||||
// https://dev.botframework.com/bots
|
|
||||||
|
|
||||||
// Connectors
|
|
||||||
// https://outlook.office.com/connectors/home/login/#/new
|
|
||||||
// https://go.microsoft.com/fwlink/?linkid=857599
|
|
||||||
|
|
||||||
import (
|
|
||||||
//"encoding/json"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Info struct {
|
|
||||||
Url string `json:"url" datastore:"url"`
|
|
||||||
Name string `json:"name" datastore:"name"`
|
|
||||||
Description string `json:"description" datastore:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actions to be done by webhooks etc
|
|
||||||
// Field is the actual field to use from json
|
|
||||||
type HookAction struct {
|
|
||||||
Type string `json:"type" datastore:"type"`
|
|
||||||
Name string `json:"name" datastore:"name"`
|
|
||||||
Id string `json:"id" datastore:"id"`
|
|
||||||
Field string `json:"field" datastore:"field"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Hook struct {
|
|
||||||
Id string `json:"id" datastore:"id"`
|
|
||||||
Info Info `json:"info" datastore:"info"`
|
|
||||||
Actions []HookAction `json:"actions" datastore:"actions"`
|
|
||||||
Type string `json:"type" datastore:"type"`
|
|
||||||
Status string `json:"status" datastore:"status"`
|
|
||||||
Running bool `json:"running" datastore:"running"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TeamsHook struct {
|
|
||||||
MembersAdded []struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
} `json:"membersAdded"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Timestamp time.Time `json:"timestamp"`
|
|
||||||
LocalTimestamp string `json:"localTimestamp"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
ChannelID string `json:"channelId"`
|
|
||||||
ServiceURL string `json:"serviceUrl"`
|
|
||||||
From struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
} `json:"from"`
|
|
||||||
Conversation struct {
|
|
||||||
IsGroup bool `json:"isGroup"`
|
|
||||||
ConversationType string `json:"conversationType"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
TenantID string `json:"tenantId"`
|
|
||||||
} `json:"conversation"`
|
|
||||||
Recipient struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
} `json:"recipient"`
|
|
||||||
ChannelData struct {
|
|
||||||
Team struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
} `json:"team"`
|
|
||||||
EventType string `json:"eventType"`
|
|
||||||
Tenant struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
} `json:"tenant"`
|
|
||||||
} `json:"channelData"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var hook Hook
|
|
||||||
var baseUrl = "https://shuffler.io"
|
|
||||||
|
|
||||||
type OauthToken struct {
|
|
||||||
TokenType string `json:"token_type"`
|
|
||||||
ExpiresIn int `json:"expires_in"`
|
|
||||||
ExtExpiresIn int `json:"ext_expires_in"`
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TeamsResponse struct {
|
|
||||||
Conversation struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
} `json:"conversation"`
|
|
||||||
From struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
} `json:"from"`
|
|
||||||
Recipient struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
} `json:"recipient"`
|
|
||||||
ReplyToId string `json:"replyToId"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type O365hook struct {
|
|
||||||
OdataContext string `json:"@odata.context"`
|
|
||||||
Value []struct {
|
|
||||||
OdataType string `json:"@odata.type"`
|
|
||||||
ID interface{} `json:"Id"`
|
|
||||||
SubscriptionID string `json:"SubscriptionId"`
|
|
||||||
SubscriptionExpirationDateTime time.Time `json:"SubscriptionExpirationDateTime"`
|
|
||||||
SequenceNumber int `json:"SequenceNumber"`
|
|
||||||
ChangeType string `json:"ChangeType"`
|
|
||||||
Resource string `json:"Resource"`
|
|
||||||
ResourceData struct {
|
|
||||||
OdataType string `json:"@odata.type"`
|
|
||||||
OdataID string `json:"@odata.id"`
|
|
||||||
OdataEtag string `json:"@odata.etag"`
|
|
||||||
ID string `json:"Id"`
|
|
||||||
} `json:"ResourceData"`
|
|
||||||
} `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Authorization(resp http.ResponseWriter, request *http.Request) {
|
|
||||||
body, err := ioutil.ReadAll(request.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Body: %s", err)
|
|
||||||
resp.WriteHeader(403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(body) > 0 {
|
|
||||||
// In here - get the email data
|
|
||||||
// Check who it belongs to and run those workflows
|
|
||||||
// This should be set from run.go with the callback
|
|
||||||
// userId: {subscriptionID: {workflowID}}
|
|
||||||
// E.g. workflow: {auth: {userId
|
|
||||||
// workflow: {trigger: {
|
|
||||||
|
|
||||||
err = forwardRequest(body)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed unmarshal: %s", err)
|
|
||||||
resp.WriteHeader(403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteHeader(200)
|
|
||||||
resp.Write([]byte("OK"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
token := request.URL.Query().Get("validationToken")
|
|
||||||
if len(token) == 0 {
|
|
||||||
log.Println("Validation token is missing")
|
|
||||||
resp.WriteHeader(403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteHeader(200)
|
|
||||||
resp.Write([]byte(string(token)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserDetails - Get one user's details from randomuser.me API
|
|
||||||
func forwardRequest(body []byte) error {
|
|
||||||
callbackUrl := os.Getenv("CALLBACKURL")
|
|
||||||
workflowId := os.Getenv("WORKFLOW_ID")
|
|
||||||
apikey := os.Getenv("FUNCTION_APIKEY")
|
|
||||||
|
|
||||||
fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute", callbackUrl, workflowId)
|
|
||||||
//log.Printf("Sending data to %s", fullUrl)
|
|
||||||
|
|
||||||
data := fmt.Sprintf(`{"execution_argument": "%s"}`, string(body))
|
|
||||||
|
|
||||||
req, err := http.NewRequest(
|
|
||||||
http.MethodPost,
|
|
||||||
fullUrl,
|
|
||||||
bytes.NewBuffer([]byte(data)),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, apikey))
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
|
||||||
randomUserClient := http.Client{
|
|
||||||
Timeout: time.Second * 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := randomUserClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Status: %d", res.StatusCode)
|
|
||||||
returnbody, err := ioutil.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("New body: %s", string(returnbody))
|
|
||||||
|
|
||||||
//log.Println(string(newbody))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"clientID": "70e37005-c954-4290-b573-d4b94e484336",
|
|
||||||
"clientSecret": ".eNw/A[kQFB5zL.agvRputdEJENeJ392",
|
|
||||||
"RedirectURL": "https://44d84ee7.ngrok.io/functions/outlook/register",
|
|
||||||
"AuthURL": "https://login.microsoftonline.com/common/oauth2/authorize",
|
|
||||||
"TokenURL": "https://login.microsoftonline.com/common/oauth2/token"
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDYDCCAkigAwIBAgIJAOvgxcclM1eyMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
|
||||||
BAYTAk5PMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
|
||||||
aWRnaXRzIFB0eSBMdGQwHhcNMTgwMzA1MTE1MTIwWhcNMjgwMzAyMTE1MTIwWjBF
|
|
||||||
MQswCQYDVQQGEwJOTzETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
|
||||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
|
||||||
CgKCAQEAowNqrvocJCLLcytYBfZhsqG3CkPsJ4PTicNyjuUGmlILPHlN1DE7jbDo
|
|
||||||
KuOKImfV4AfQANnttaPksZyuKJL8XGpC7YmF5mmInUWG48SmZjvRbBi1LnCrjJKS
|
|
||||||
ywh2lJqw4w30w2ItcpogDrYhh6+T3VyabcYngZjKSFgON3wo4I0c6aT19VVXGqnK
|
|
||||||
y1WEejZmiChV4iwEu4vMPIzt16QpIBr8NPSkBLLRDAGWMjFnIuYDEwgjVn6XYhM9
|
|
||||||
+NxhvY9es+qeqLQsZj2a1wcDGaw7G4iNZdltlPmlTCreRDBYBTsYCds/rJmPZ2xi
|
|
||||||
6jgiyNZj3xHG5Knw2YIw0OwHyT9mWQIDAQABo1MwUTAdBgNVHQ4EFgQUckr5tCzg
|
|
||||||
F1eBID7mtWTfeqyX4g0wHwYDVR0jBBgwFoAUckr5tCzgF1eBID7mtWTfeqyX4g0w
|
|
||||||
DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAAdH1aPcWBjJHF6n/
|
|
||||||
NgIiRSEU4mi5I6RUlPuR7dN7dcmF1Ho7quurNFzknXwks+a62oKSnkkFxxwrv/d6
|
|
||||||
dIH5kNibVs7oRxEpA3gUmXXKUW4RPrxAp2zN37t7zs5xbpTATxfJiIMP8Rjo0sOf
|
|
||||||
SilS22Sn0e0HxBi78t3DJEZvOQ9KSRuD1g9gOAY4lj/fni6rVJo8YCR2MyjmQoXB
|
|
||||||
luHDF4jTqi/TkXECfqQZu0pctx3maISpB1fAuaELwPDvqbLgoC97gl6bIEiKLkJ0
|
|
||||||
JkbGnb997K80ztFvFAKGyUtsvEDCupe/fdBPFqCruAQWI/BqVJFTdRD43dWEQANF
|
|
||||||
S8ApvA==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEowIBAAKCAQEAowNqrvocJCLLcytYBfZhsqG3CkPsJ4PTicNyjuUGmlILPHlN
|
|
||||||
1DE7jbDoKuOKImfV4AfQANnttaPksZyuKJL8XGpC7YmF5mmInUWG48SmZjvRbBi1
|
|
||||||
LnCrjJKSywh2lJqw4w30w2ItcpogDrYhh6+T3VyabcYngZjKSFgON3wo4I0c6aT1
|
|
||||||
9VVXGqnKy1WEejZmiChV4iwEu4vMPIzt16QpIBr8NPSkBLLRDAGWMjFnIuYDEwgj
|
|
||||||
Vn6XYhM9+NxhvY9es+qeqLQsZj2a1wcDGaw7G4iNZdltlPmlTCreRDBYBTsYCds/
|
|
||||||
rJmPZ2xi6jgiyNZj3xHG5Knw2YIw0OwHyT9mWQIDAQABAoIBABJ+9L/dyQuglwz+
|
|
||||||
QgKLLhKinq4fftAM+ReMgZcNDW69GGFIMjh9TZCKHg2fu7Cjr3S37jXqhDoz2mL8
|
|
||||||
sBYSd2fU9rsU+4hlOQb/OIrnaSn4Z46oTwZx6kUM7HL1Bt9dnexlTPxOS3HRYwnI
|
|
||||||
SI2oslJPi4YhEaJ2v5ztwM8y20B/E/zSW2onWz5gB8/bdxSmuJaWfHioIEoac8Gf
|
|
||||||
BE7jiYMnx9kKeVfgkKPMBKXhAyE2lbAz7N5nDS/4HkbUMh389RBvakc4gkv/QkSW
|
|
||||||
LNuXpbcSqJiG0FcVutjYS87a/ul3IdhAYmZDTuvRUhNsWBiY3LSY4C9NlHhiJihg
|
|
||||||
RU1kknECgYEA1dPT6n4hy/OK2mT3ThGQQFMJWsnmcHSsvuK31+UYZOI9kNtBVxc6
|
|
||||||
HSHkh0G53o6o2wZLr2gMXy35O5ZxSbectA1Q4MJDlYBs1MfHdOVyE4z6mmA9TLN1
|
|
||||||
c+9pYOC0qx+6NwPdO7j2xdUMEUfzocWOeay0AzJg20BQYmKuy3Kc+tcCgYEAwyn6
|
|
||||||
n+XS0vodfJdHhvbW/jocQlHQOBK5HklZfq2PMgpRRDaBOvHP/f07egLc2inec2sC
|
|
||||||
yPSCEfRMMhFcU5NoBt4Unzz2Y8pbpL1L5kbM6B4IqK/5vYcbvkmBkhL3AFT6Fg/3
|
|
||||||
3XCdygPW9Vf1nRKr2KhT9dDvB+XO2B75JmKwMk8CgYApKzGf8kz7gZZ4WfwrccI+
|
|
||||||
QD6K1lihyjUAQ5J15Mv/kHeeDjjUVcqAlWf0irkImpr0IJAt43COWsGjsWF6efmX
|
|
||||||
yQCLZZuxixppFVXXsd122ivd0S28OMkiWzQEzP67+83Ujc/okcIhcNVz9lB4ExtN
|
|
||||||
Xe0CuI5haE6RwsI4tYZ33QKBgFq6ckPRcOAZ3IlmPp9Us3/+fdKq/BSFR7/3s347
|
|
||||||
q11FBKCkghFoBxx5lCPVntxhKIQZlHLdkHZOTvnbrkNAPNUsewPIMHcVxOLiCZ3k
|
|
||||||
/i9OfxIEtSJR5CjjPTQuUtu5pYWKKN2uE/ytKkpmeM1rt64CGv4lAmp2gGFijMs2
|
|
||||||
h9jrAoGBANPQO6cKqtnxvst3lnljVBoftlJgeHamUac+xeYKA5Hocv5VwLXMTzzu
|
|
||||||
09tAhQFvFwCWWrfdgtvIM6k5Sl9F5MdiO9VNflI0IVudIcm9FKorWogH02mtwxsw
|
|
||||||
hvk5VUk3awiZ/Nu9t38ukeqCetjQEf6yupy/14ZPLndN5naSeEwo
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/gorilla/handlers"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func webhook() {
|
|
||||||
// FIXME - remove static
|
|
||||||
port := ":8080"
|
|
||||||
baseFilePath := "/"
|
|
||||||
|
|
||||||
mux := mux.NewRouter()
|
|
||||||
mux.SkipClean(true)
|
|
||||||
|
|
||||||
// FIXME - Add path for updating the hook? Can be a specific POST requeuest from backend
|
|
||||||
mux.HandleFunc(baseFilePath, Authorization).Methods("POST")
|
|
||||||
mux.HandleFunc("/authorize", Authorization).Methods("POST")
|
|
||||||
|
|
||||||
handlers.LoggingHandler(os.Stdout, mux)
|
|
||||||
loggedRouter := handlers.LoggingHandler(os.Stdout, mux)
|
|
||||||
|
|
||||||
log.Printf("Starting on http://localhost%s", port)
|
|
||||||
err := http.ListenAndServe(
|
|
||||||
port,
|
|
||||||
loggedRouter,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("ListenAndServer: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
webhook()
|
|
||||||
}
|
|
||||||
@@ -1,304 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// This entire script should be part of the API backend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Subscription struct {
|
|
||||||
ChangeType string `json:"changeType"`
|
|
||||||
NotificationURL string `json:"notificationUrl"`
|
|
||||||
Resource string `json:"resource"`
|
|
||||||
ExpirationDateTime string `json:"expirationDateTime"`
|
|
||||||
ClientState string `json:"clientState"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientState string `json:"ClientState,omitempty"`
|
|
||||||
// OdataType string `json:"@odata.type"`
|
|
||||||
|
|
||||||
//odata.type - Include "@odata.type":"#Microsoft.OutlookServices.PushSubscription". The PushSubscription entity defines NotificationURL.
|
|
||||||
//ChangeType - Specifies the types of events to monitor for that resource. See ChangeType for the supported types.
|
|
||||||
//ClientState - Optional property that indicates that each notification should be sent with a header by the same ClientState value. This lets the listener check the legitimacy of each notification.
|
|
||||||
//NotificationURL - Specifies where notifications should be sent to. This URL represents a web service typically implemented by the client.
|
|
||||||
//Resource - Specifies the resource to monitor and receive notifications on. You can use the optional query parameter $filter to refine the conditions for a notification, or use $select to include specific properties in a rich notification.
|
|
||||||
|
|
||||||
//https://outlook.office.com/mail.read
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
ClientID string
|
|
||||||
ClientSecret string
|
|
||||||
RedirectUrl string
|
|
||||||
AuthUrl string
|
|
||||||
TokenUrl string
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOfficeAppInfo() (Config, error) {
|
|
||||||
configpath := "integrations/config.json"
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(configpath)
|
|
||||||
if err != nil {
|
|
||||||
//log.Fatal(err)
|
|
||||||
log.Printf("Error getting hive: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := Config{}
|
|
||||||
err = json.Unmarshal(data, &config)
|
|
||||||
if err != nil {
|
|
||||||
return Config{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This should be a popup for the user
|
|
||||||
func get_accesstoken() (*http.Client, OauthToken, error) {
|
|
||||||
ctx := context.Background()
|
|
||||||
config, err := getOfficeAppInfo()
|
|
||||||
if err != nil {
|
|
||||||
return nil, OauthToken{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := &oauth2.Config{
|
|
||||||
ClientID: config.ClientID,
|
|
||||||
ClientSecret: config.ClientSecret,
|
|
||||||
Scopes: []string{
|
|
||||||
"Mail.Read",
|
|
||||||
"User.Read",
|
|
||||||
"https://outlook.office.com/mail.read",
|
|
||||||
},
|
|
||||||
RedirectURL: "https://localhost:8000",
|
|
||||||
Endpoint: oauth2.Endpoint{
|
|
||||||
AuthURL: config.AuthUrl,
|
|
||||||
TokenURL: config.TokenUrl,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
//"Mail.Read.Shared",
|
|
||||||
|
|
||||||
//url := conf.AuthCodeURL("state", oauth2.SetAuthURLParam("resource", "https://outlook.office.com"))
|
|
||||||
// ADD DATA TO STATE HERE :O
|
|
||||||
url := conf.AuthCodeURL("workflow_id%3Dc2e0b50a-2957-427e-a97b-b989dc5a5408%26trigger_id%3D9e845679-5843-4959-a76c-a6d664e9df35%26username%3Drheyix.yt@gmail.com", oauth2.SetAuthURLParam("resource", "https://graph.microsoft.com"))
|
|
||||||
|
|
||||||
fmt.Printf("Visit the URL for the auth dialog: \n%v\n\n", url)
|
|
||||||
codechannel := make(chan string)
|
|
||||||
|
|
||||||
// Handles the server callback, listening on port 8000
|
|
||||||
go func() {
|
|
||||||
port := ":8000"
|
|
||||||
|
|
||||||
http.HandleFunc("/", func(response http.ResponseWriter, request *http.Request) {
|
|
||||||
tmpcode := request.URL.Query().Get("code")
|
|
||||||
if len(tmpcode) < 100 {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
codechannel <- tmpcode
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// FIX - might cause errors not being printed
|
|
||||||
err := http.ListenAndServeTLS(port, "integrations/server.crt", "integrations/server.key", nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("%s\n", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
code := <-codechannel
|
|
||||||
close(codechannel)
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/52787420/multiple-resources-in-a-single-authorization-request
|
|
||||||
// Multi resource ^
|
|
||||||
access_token, err := conf.Exchange(ctx, code)
|
|
||||||
//log.Printf("%#v", access_token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, OauthToken{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//log.Printf("%#v", access_token)
|
|
||||||
outlookClient := conf.Client(ctx, access_token)
|
|
||||||
|
|
||||||
oauthToken := OauthToken{
|
|
||||||
AccessToken: access_token.AccessToken,
|
|
||||||
TokenType: access_token.TokenType,
|
|
||||||
RefreshToken: access_token.RefreshToken,
|
|
||||||
Expiry: access_token.Expiry,
|
|
||||||
}
|
|
||||||
|
|
||||||
return outlookClient, oauthToken, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type OauthToken struct {
|
|
||||||
AccessToken string `json:"AccessToken" datastore:"AccessToken,noindex"`
|
|
||||||
TokenType string `json:"TokenType" datastore:"TokenType,noindex"`
|
|
||||||
RefreshToken string `json:"RefreshToken" datastore:"RefreshToken,noindex"`
|
|
||||||
Expiry time.Time `json:"Expiry" datastore:"Expiry,noindex"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mailfolders struct {
|
|
||||||
OdataContext string `json:"@odata.context"`
|
|
||||||
OdataNextLink string `json:"@odata.nextLink"`
|
|
||||||
Value []struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
DisplayName string `json:"displayName"`
|
|
||||||
ParentFolderID string `json:"parentFolderId"`
|
|
||||||
ChildFolderCount int `json:"childFolderCount"`
|
|
||||||
UnreadItemCount int `json:"unreadItemCount"`
|
|
||||||
TotalItemCount int `json:"totalItemCount"`
|
|
||||||
} `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFolders(client *http.Client) (Mailfolders, error) {
|
|
||||||
requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/users/frikky@shuffletest.onmicrosoft.com/mailfolders")
|
|
||||||
|
|
||||||
ret, err := client.Get(requestUrl)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("FolderErr: %s", err)
|
|
||||||
return Mailfolders{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Status folders: %d", ret.StatusCode)
|
|
||||||
body, err := ioutil.ReadAll(ret.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Body: %s", err)
|
|
||||||
return Mailfolders{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//log.Printf("Body: %s", string(body))
|
|
||||||
|
|
||||||
mailfolders := Mailfolders{}
|
|
||||||
err = json.Unmarshal(body, &mailfolders)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unmarshal: %s", err)
|
|
||||||
return Mailfolders{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//fmt.Printf("%#v", mailfolders)
|
|
||||||
// FIXME - recursion for subfolders
|
|
||||||
// Recursive struct
|
|
||||||
// folderEndpoint := fmt.Sprintf("%s/%s/childfolders?$top=40", requestUrl, parentId)
|
|
||||||
for _, folder := range mailfolders.Value {
|
|
||||||
log.Println(folder.DisplayName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return mailfolders, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribes to a mailbox based on some thingies
|
|
||||||
func makeSubscription(client *http.Client, folderIds []string) {
|
|
||||||
// FIXME - show the users folders from oauth and let them choose
|
|
||||||
|
|
||||||
fullUrl := "https://graph.microsoft.com/v1.0/subscriptions"
|
|
||||||
//resource := fmt.Sprintf("https://outlook.office.com/api/v2.0/me/mailfolders('inbox')/messages")
|
|
||||||
resource := fmt.Sprintf("me/mailfolders('inbox')/messages")
|
|
||||||
sub := Subscription{
|
|
||||||
ChangeType: "created",
|
|
||||||
NotificationURL: "https://de4fc12b.ngrok.io",
|
|
||||||
ExpirationDateTime: "2019-09-22T18:23:45.9356913Z",
|
|
||||||
ClientState: "This is a test",
|
|
||||||
Resource: resource,
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.Marshal(sub)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Marshal: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf(string(data))
|
|
||||||
|
|
||||||
req, err := http.NewRequest(
|
|
||||||
"POST",
|
|
||||||
fullUrl,
|
|
||||||
bytes.NewBuffer(data),
|
|
||||||
)
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
|
||||||
|
|
||||||
res, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Client: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Status: %d", res.StatusCode)
|
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Body: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(string(body))
|
|
||||||
log.Println("Shoooould be set up :)")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOutlookClient(code string, accessToken OauthToken, redirectUri string) (*http.Client, *oauth2.Token, error) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
conf := &oauth2.Config{
|
|
||||||
ClientID: "70e37005-c954-4290-b573-d4b94e484336",
|
|
||||||
ClientSecret: ".eNw/A[kQFB5zL.agvRputdEJENeJ392",
|
|
||||||
Scopes: []string{
|
|
||||||
"Mail.Read",
|
|
||||||
"User.Read",
|
|
||||||
"https://outlook.office.com/mail.read",
|
|
||||||
},
|
|
||||||
RedirectURL: redirectUri,
|
|
||||||
Endpoint: oauth2.Endpoint{
|
|
||||||
AuthURL: "https://login.microsoftonline.com/common/oauth2/authorize",
|
|
||||||
TokenURL: "https://login.microsoftonline.com/common/oauth2/token",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(code) > 0 {
|
|
||||||
access_token, err := conf.Exchange(ctx, code)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Access_token issue: %s", err)
|
|
||||||
return &http.Client{}, access_token, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := conf.Client(ctx, access_token)
|
|
||||||
return client, access_token, nil
|
|
||||||
} else {
|
|
||||||
// Manually recreate the oauthtoken
|
|
||||||
access_token := &oauth2.Token{
|
|
||||||
AccessToken: accessToken.AccessToken,
|
|
||||||
TokenType: accessToken.TokenType,
|
|
||||||
RefreshToken: accessToken.RefreshToken,
|
|
||||||
Expiry: accessToken.Expiry,
|
|
||||||
}
|
|
||||||
|
|
||||||
client := conf.Client(ctx, access_token)
|
|
||||||
return client, access_token, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
graphclient, oauthToken, err := get_accesstoken()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Oauth setup: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME - make this possible for alternative users (shared)
|
|
||||||
folders, err := getFolders(graphclient)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Folder get error: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = folders
|
|
||||||
|
|
||||||
folderIds := []string{"inbox"}
|
|
||||||
//log.Println(folders)
|
|
||||||
//log.Printf("%#v", oauthToken)
|
|
||||||
// Use oauthToken to generate data for outlook
|
|
||||||
outlookclient, _, err := getOutlookClient("", oauthToken, "https://localhost:8000")
|
|
||||||
makeSubscription(outlookclient, folderIds)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user