From df0d1fd3154782391c8410b63e045f07ac8fb7cc Mon Sep 17 00:00:00 2001 From: Lev <1187448+levb@users.noreply.github.com> Date: Wed, 13 Nov 2019 07:01:08 -0800 Subject: [PATCH] Initial implementation: supports connect and viewcal, untested (#1) * command + http framework * Got a 404 on the hardcoded Mattermost Org - no Outlook * `/msoffice viewcal` works on a personal account - added OAuth2 credentials a config values * PR feedback --- Makefile | 2 +- go.mod | 23 +-- go.sum | 171 ++++++++++++++------ plugin.json | 41 ++++- server/command/command.go | 126 +++++++++++++++ server/command/connect.go | 17 ++ server/command/help.go | 23 +++ server/command/info.go | 21 +++ server/command/view_calendar.go | 27 ++++ server/config/config.go | 36 +++++ server/config/const.go | 11 ++ server/configuration.go | 83 ---------- server/http/api_authorized.go | 12 ++ server/http/http.go | 89 ++++++++++ server/http/oauth2_complete.go | 97 +++++++++++ server/http/oauth2_connect.go | 36 +++++ server/kvstore/hashed_key.go | 45 ++++++ server/kvstore/hashed_key_test.go | 33 ++++ server/kvstore/keys.go | 4 + server/kvstore/kvstore.go | 57 +++++++ server/kvstore/ots.go | 37 +++++ server/kvstore/plugin_store.go | 63 ++++++++ server/main.go | 19 ++- server/manifest.go | 2 +- server/msgraph/client.go | 54 +++++++ server/msgraph/get_me.go | 10 ++ server/msgraph/get_user_calendar.go | 10 ++ server/plugin.go | 28 ---- server/plugin/authorization.go | 30 ++++ server/plugin/plugin.go | 206 ++++++++++++++++++++++++ server/plugin_test.go | 27 ---- server/user/oauth2_store.go | 46 ++++++ server/user/user.go | 30 ++++ server/user/user_store.go | 71 ++++++++ server/utils/bot_poster.go | 60 +++++++ server/utils/byte_size.go | 89 ++++++++++ server/utils/limited_readcloser.go | 40 +++++ server/utils/limited_readcloser_test.go | 47 ++++++ server/utils/utils.go | 52 ++++++ server/utils/utils_test.go | 140 ++++++++++++++++ webapp/src/manifest.js | 2 +- 41 files changed, 1799 insertions(+), 218 deletions(-) create mode 100644 server/command/command.go create mode 100644 server/command/connect.go create mode 100644 server/command/help.go create mode 100644 server/command/info.go create mode 100644 server/command/view_calendar.go create mode 100644 server/config/config.go create mode 100644 server/config/const.go delete mode 100644 server/configuration.go create mode 100644 server/http/api_authorized.go create mode 100644 server/http/http.go create mode 100644 server/http/oauth2_complete.go create mode 100644 server/http/oauth2_connect.go create mode 100644 server/kvstore/hashed_key.go create mode 100644 server/kvstore/hashed_key_test.go create mode 100644 server/kvstore/keys.go create mode 100644 server/kvstore/kvstore.go create mode 100644 server/kvstore/ots.go create mode 100644 server/kvstore/plugin_store.go create mode 100644 server/msgraph/client.go create mode 100644 server/msgraph/get_me.go create mode 100644 server/msgraph/get_user_calendar.go delete mode 100644 server/plugin.go create mode 100644 server/plugin/authorization.go create mode 100644 server/plugin/plugin.go delete mode 100644 server/plugin_test.go create mode 100644 server/user/oauth2_store.go create mode 100644 server/user/user.go create mode 100644 server/user/user_store.go create mode 100644 server/utils/bot_poster.go create mode 100644 server/utils/byte_size.go create mode 100644 server/utils/limited_readcloser.go create mode 100644 server/utils/limited_readcloser_test.go create mode 100644 server/utils/utils.go create mode 100644 server/utils/utils_test.go diff --git a/Makefile b/Makefile index bf2345ce..8ae654cc 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ apply: ## Runs govet and gofmt against all packages. .PHONY: check-style -check-style: webapp/.npminstall gofmt govet golint +check-style: webapp/.npminstall gofmt govet @echo Checking for style guide compliance ifneq ($(HAS_WEBAPP),) diff --git a/go.mod b/go.mod index 27dc4279..0deecbb9 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,13 @@ -module github.com/mattermost/mattermost-plugin-starter-template +module github.com/mattermost/mattermost-plugin-msoffice -go 1.12 +go 1.13 require ( - github.com/blang/semver v3.6.1+incompatible // indirect - github.com/go-ldap/ldap v3.0.3+incompatible // indirect - github.com/hashicorp/go-hclog v0.9.2 // indirect - github.com/hashicorp/go-plugin v1.0.1 // indirect - github.com/lib/pq v1.1.1 // indirect - github.com/mattermost/go-i18n v1.11.0 // indirect - github.com/mattermost/mattermost-server v5.12.0+incompatible - github.com/pelletier/go-toml v1.4.0 // indirect + github.com/gorilla/mux v1.7.3 + github.com/jkrecek/msgraph-go v0.0.0-20190328140430-9f7466d8cb1f + github.com/mattermost/mattermost-server v0.0.0-20190927121038-340287890a78 github.com/pkg/errors v0.8.1 + github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad // indirect github.com/stretchr/testify v1.3.0 -) - -replace ( - git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999 - // Workaround for https://github.com/golang/go/issues/30831 and fallout. - github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8f45f776aaf1 + golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914 ) diff --git a/go.sum b/go.sum index 01f9b4b5..220f25e3 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.1 h1:2kHhTjz+eKEI7tt3Fqf5j3APCq+z9tuY2CzeCIxTo+A= cloud.google.com/go v0.37.1/go.mod h1:SAbnLi6YTSPKSI0dTUEOVLCkyPfKXK8n4ibqiMoj4ok= contrib.go.opencensus.io/exporter/ocagent v0.4.9/go.mod h1:ueLzZcP7LPhPulEBukGn4aLh7Mx9YJwpVJ9nL2FYltw= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= @@ -9,44 +10,55 @@ github.com/Azure/azure-sdk-for-go v26.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/squirrel v1.1.0 h1:baP1qLdoQCeTw3ifCdOq2dkYc6vGcmRdaociKLbEJXs= github.com/Masterminds/squirrel v1.1.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA= +github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788/go.mod h1:lY1dZd8HBzJ10eqKERHn3CU59tfhzcAVb2c0ZhIWSOk= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/a8m/mark v0.1.1-0.20170507133748-44f2db618845/go.mod h1:c8Mh99Cw82nrsAnPgxQSZHkswVOJF7/MqZb1ZdvriLM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/avct/uasurfer v0.0.0-20190308134847-43c6f9a90eeb/go.mod h1:noBAuukeYOXa0aXGqxr24tADqkwDO2KRD15FsuaZ5a8= github.com/aws/aws-sdk-go v1.19.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver v3.6.1+incompatible h1:RmA4CjDkwiAdjCfRQH6UZRp45n/uV30JiSkad6Acn/8= -github.com/blang/semver v3.6.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/corpix/uarand v0.0.0/go.mod h1:JSm890tOkDN+M1jqN8pUGDKnzJrsVbJwSMHBY4zwz7M= +github.com/corpix/uarand v0.1.0/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3/go.mod h1:hEfFauPHz7+NnjR/yHJGhrKo1Za+zStgwUETx3yzqgY= github.com/die-net/lrucache v0.0.0-20181227122439-19a39ef22a11/go.mod h1:ew0MSjCVDdtGMjF3kzLK9hwdgF5mOE8SbYVF3Rc7mkU= github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8 h1:6muCmMJat6z7qptVrIf/+OWPxsjAfvhw5/6t+FwEkgg= github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8/go.mod h1:nYia/MIs9OyvXXYboPmNOj0gVWo97Wx0sde+ZuKkoM4= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= @@ -59,15 +71,14 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/gernest/wow v0.1.0/go.mod h1:dEPabJRi5BneI1Nev1VWo0ZlcTWibHWp43qxKms4elY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-gorp/gorp v2.0.0+incompatible h1:dIQPsBtl6/H1MjVseWuWPXa7ET4p6Dve4j3Hg+UjqYw= github.com/go-gorp/gorp v2.0.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= -github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk= -github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -79,18 +90,23 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20190227174305-8f45f776aaf1/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -100,32 +116,35 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hako/durafmt v0.0.0-20180520121703-7b7ae1e72ead/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= +github.com/hako/durafmt v0.0.0-20190612201238-650ed9f29a84/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= -github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-plugin v1.0.0/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -133,7 +152,7 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/memberlist v0.1.4-0.20190312092157-a8f83c6403e0/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= @@ -141,13 +160,16 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jaytaylor/html2text v0.0.0-20190311042500-a93a6c6ea053/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= -github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= +github.com/jamiealquiza/envy v1.1.0/go.mod h1:MP36BriGCLwEHhi1OU8E9569JNZrjWfCvzG7RsPnHus= +github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43 h1:jTkyeF7NZ5oIr0ESmcrpiDgAfoidCBF4F5kJhjtaRwE= +github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jkrecek/msgraph-go v0.0.0-20190328140430-9f7466d8cb1f h1:uqEdY/CRO5gpKubf8vgCIUOL9TLy0dWBKDtjlYddkvo= +github.com/jkrecek/msgraph-go v0.0.0-20190328140430-9f7466d8cb1f/go.mod h1:RBokY5v/eWZg8XHsAUPtyqoCA646rL/NV8Br2XISja0= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -165,31 +187,35 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= -github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/mattermost/go-i18n v1.1.2/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34= github.com/mattermost/go-i18n v1.11.0 h1:1hLKqn/ZvhZ80OekjVPGYcCrBfMz+YxNNgqS+beL7zE= github.com/mattermost/go-i18n v1.11.0/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34= github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible h1:FN4zK2wNig7MVVsOsGEZ+LeIq0gUcudn3LEGgbodMq8= github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible/go.mod h1:0kX1qa3DOpaPJyOdMLeo7TcBN0QmUszj9a/VygOhDe0= -github.com/mattermost/mattermost-server v5.12.0+incompatible h1:Glsf3vGsceqzaDa9BQfO5Wsj0cNxagwuBhy3pCN/AT0= -github.com/mattermost/mattermost-server v5.12.0+incompatible/go.mod h1:O3Tfw/SymCK+cYdFSMjQZyP4VndjcfVeT6ZoJjglByw= +github.com/mattermost/ldap v3.0.4+incompatible h1:SOeNnz+JNR+foQ3yHkYqijb9MLPhXN2BZP/PdX23VDU= +github.com/mattermost/ldap v3.0.4+incompatible/go.mod h1:b4reDCcGpBxJ4WX0f224KFY+OR0npin7or7EFpeIko4= +github.com/mattermost/mattermost-server v0.0.0-20190927121038-340287890a78 h1:2O+HJDCLWxCQMsoUSpuJdN5L/zF7Ng8piqoe5PB0JAY= +github.com/mattermost/mattermost-server v0.0.0-20190927121038-340287890a78/go.mod h1:M8AZ113Nuu+XvJMPOIzNx55xH1zGVltmcP3A3FAHHtw= github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs= github.com/mattermost/viper v1.0.4/go.mod h1:uc5hKG9lv4/KRwPOt2c1omOyirS/UnuA2TytiZQSFHM= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= -github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.6/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/minio/minio-go v0.0.0-20190422205105-a8704b60278f/go.mod h1:/haSOWG8hQNx2+JOfLJ9GKp61EAmgPwRVw/Sac0NzaM= +github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/minio/cli v1.20.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY= +github.com/minio/minio-go/v6 v6.0.34/go.mod h1:vaNT59cWULS37E+E9zkuN/BVnKHyXtVGS+b04Boc66Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -197,11 +223,16 @@ github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdI github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/muesli/smartcrop v0.2.1-0.20181030220600-548bbf0c0965/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI= +github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -218,7 +249,6 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.3.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/peterbourgon/diskv v0.0.0-20171120014656-2973218375c3/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -231,50 +261,65 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190322151404-55ae3d9d5573/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/rwcarlsen/goexif v0.0.0-20190318171057-76e3344f7516/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v0.0.0-20180103174451-36e9d2ebbde5/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/segmentio/analytics-go v2.0.1-0.20160426181448-2d840d861c32+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= +github.com/segmentio/analytics-go v3.0.1+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= -github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/throttled/throttled v2.2.4+incompatible/go.mod h1:0BjlrEGQmvxps+HuXLsyRdqpSRvJpq0PNIsOtqP9Nos= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tylerb/graceful v1.2.15/go.mod h1:LPYTbOYmUTdabwRt0TGhLllQ0MUNbs0Y5q1WXJOI9II= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -284,30 +329,34 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M= -go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190128193316-c7b33c32a30b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -315,6 +364,7 @@ golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -322,12 +372,16 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 h1:FP8hkuE6yUEaJnK7O2eTuejKWwW+Rhfj80dQ2JcKxCU= -golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914 h1:jIOcLT9BZzyJ9ce+IwwZ+aF9yeCqzrR+NrD68a/SHKw= golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -346,13 +400,15 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 h1:cGjJzUd8RgBw428LXP65YXni0aiGNA4Bl+ls8SmLOm8= -golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -364,8 +420,11 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= @@ -376,6 +435,8 @@ google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -383,12 +444,16 @@ google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190321212433-e79c0c59cdb5 h1:VchCZUJA1Lkjn3FxAtLPl4GotxoGt/E8ZIm9nVqbhQ8= google.golang.org/genproto v0.0.0-20190321212433-e79c0c59cdb5/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 h1:Ygq9/SRJX9+dU0WCIICM8RkWvDw03lvB77hrhJnpxfU= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= @@ -399,15 +464,16 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/olivere/elastic.v5 v5.0.79/go.mod h1:uhHoB4o3bvX5sorxBU29rPcmBQdV2Qfg0FBrx5D6pV0= +gopkg.in/olivere/elastic.v5 v5.0.81/go.mod h1:uhHoB4o3bvX5sorxBU29rPcmBQdV2Qfg0FBrx5D6pV0= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= @@ -417,5 +483,6 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= willnorris.com/go/gifresize v1.0.0/go.mod h1:eBM8gogBGCcaH603vxSpnfjwXIpq6nmnj/jauBDKtAk= -willnorris.com/go/imageproxy v0.8.1-0.20190326225038-d4246a08fdec/go.mod h1:CKIesng3W4D4npJaypowqkxF33s3AH7EuohbR1miBGw= +willnorris.com/go/imageproxy v0.9.0/go.mod h1:SVC/wfHtCS4kjk3llMeuV4KlTN3a8XTgFWI8o7i3Avg= diff --git a/plugin.json b/plugin.json index b5b9a76f..75ca39c3 100644 --- a/plugin.json +++ b/plugin.json @@ -1,9 +1,9 @@ { - "id": "com.mattermost.plugin-starter-template", - "name": "Plugin Starter Template", - "description": "This plugin serves as a starting point for writing a Mattermost plugin.", + "id": "com.mattermost.msoffice", + "name": "TODO:name", + "description": "TODO: description.", "version": "0.1.0", - "min_server_version": "5.12.0", + "min_server_version": "5.16.0", "server": { "executables": { "linux-amd64": "server/dist/plugin-linux-amd64", @@ -16,7 +16,34 @@ }, "settings_schema": { "header": "", - "footer": "", - "settings": [] + "settings": [ + { + "key": "AdminUserIDs", + "display_name": "Admin User IDs:", + "type": "text", + "help_text": "List of users authorized to administer the plugin in addition to the System Admins. Must be a comma-separated list of user IDs.\n \nUser IDs can be found by navigating to **System Console** > **User Management**. After clicking into a user's name, their ID is on the right-hand side of the blue header." + }, + { + "key": "OAuth2Authority", + "display_name": "Microsoft Office Authority (\"common\", or application tenant ID)", + "type": "text", + "help_text": "\"common\", or application tenant ID.", + "default": "common" + }, + { + "key": "OAuth2ClientId", + "display_name": "Microsoft Office Client ID", + "type": "text", + "help_text": "Microsoft Office Client ID.", + "default": "" + }, + { + "key": "OAuth2ClientSecret", + "display_name": "Microsoft Office Client Secret", + "type": "text", + "help_text": "Microsoft Office Client Secret.", + "default": "" + } + ] } -} +} \ No newline at end of file diff --git a/server/command/command.go b/server/command/command.go new file mode 100644 index 00000000..10b32e46 --- /dev/null +++ b/server/command/command.go @@ -0,0 +1,126 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package command + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/plugin" + + "github.com/mattermost/mattermost-plugin-msoffice/server/config" + "github.com/mattermost/mattermost-plugin-msoffice/server/user" + "github.com/mattermost/mattermost-plugin-msoffice/server/utils" +) + +// Handler handles commands +type Handler struct { + Config *config.Config + UserStore user.Store + API plugin.API + BotPoster utils.BotPoster + IsAuthorizedAdmin func(userId string) (bool, error) + + Context *plugin.Context + Args *model.CommandArgs + ChannelId string + MattermostUserId string + User *user.User +} + +func getHelp() string { + help := ` +TODO: help text. +` + return codeBlock(fmt.Sprintf( + help, + )) +} + +// Register is a function that allows the runner to register commands with the mattermost server. +type RegisterFunc func(*model.Command) error + +// Init should be called by the plugin to register all necessary commands +func Register(registerFunc RegisterFunc) { + _ = registerFunc(&model.Command{ + Trigger: config.CommandTrigger, + DisplayName: "TODO display name", + Description: "TODO description", + AutoComplete: true, + AutoCompleteDesc: "TODO autocomplete desc", + AutoCompleteHint: "TODO autocomplete hint", + }) +} + +// Execute should be called by the plugin when a command invocation is received from the Mattermost server. +func (h *Handler) Handle() (string, error) { + cmd, parameters, err := h.isValid() + if err != nil { + return "", err + } + + h.MattermostUserId = h.Args.UserId + auth, err := h.IsAuthorizedAdmin(h.MattermostUserId) + if err != nil { + return "", errors.WithMessage(err, "Failed to get authorization. Please contact your system administrator.\nFailure") + } + if !auth { + return "", errors.New("You must be authorized to use the plugin. Please contact your system administrator.") + } + + handler := h.help + switch cmd { + case "info": + handler = h.info + case "connect": + handler = h.connect + case "viewcal": + handler = h.viewCalendar + } + out, err := handler(parameters...) + if err != nil { + return "", errors.WithMessagef(err, "Command /%s %s failed", config.CommandTrigger, cmd) + } + + return out, nil +} + +func (h *Handler) loadRemoteUser() (*user.User, error) { + user := user.User{} + err := h.UserStore.LoadRemoteUser(h.MattermostUserId, &user) + if err != nil { + return nil, err + } + return &user, nil +} + +func (h *Handler) isValid() (subcommand string, parameters []string, err error) { + if h.Context == nil || h.Args == nil || h.Config.MattermostSiteURL == "" { + return "", nil, errors.New("Invalid arguments to command.Handler") + } + + split := strings.Fields(h.Args.Command) + command := split[0] + if command != "/"+config.CommandTrigger { + return "", nil, errors.Errorf("%q is not a supported command. Please contact your system administrator.", command) + } + + parameters = []string{} + subcommand = "" + if len(split) > 1 { + subcommand = split[1] + } + if len(split) > 2 { + parameters = split[2:] + } + + return subcommand, parameters, nil +} + +func codeBlock(in string) string { + return fmt.Sprintf("```\n%s\n```", in) +} diff --git a/server/command/connect.go b/server/command/connect.go new file mode 100644 index 00000000..d6b4e6f9 --- /dev/null +++ b/server/command/connect.go @@ -0,0 +1,17 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package command + +import ( + "fmt" + + "github.com/mattermost/mattermost-plugin-msoffice/server/config" +) + +func (r *Handler) connect(parameters ...string) (string, error) { + out := fmt.Sprintf("[Click here to link your %s account.](%s/oauth2/connect)", + config.ApplicationName, + r.Config.PluginURL) + return out, nil +} diff --git a/server/command/help.go b/server/command/help.go new file mode 100644 index 00000000..9aa61968 --- /dev/null +++ b/server/command/help.go @@ -0,0 +1,23 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package command + +import ( + "fmt" + + "github.com/mattermost/mattermost-plugin-msoffice/server/config" +) + +func (r *Handler) help(parameters ...string) (string, error) { + resp := fmt.Sprintf("Mattermost Microsoft Office plugin version: %s, "+ + "[%s](https://github.com/mattermost/%s/commit/%s), built %s\n", + r.Config.PluginVersion, + r.Config.BuildHashShort, + config.Repository, + r.Config.BuildHash, + r.Config.BuildDate) + resp += "\n" + resp += "TODO help" + return resp, nil +} diff --git a/server/command/info.go b/server/command/info.go new file mode 100644 index 00000000..09f95cd4 --- /dev/null +++ b/server/command/info.go @@ -0,0 +1,21 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package command + +import ( + "fmt" + + "github.com/mattermost/mattermost-plugin-msoffice/server/config" +) + +func (r *Handler) info(parameters ...string) (string, error) { + resp := fmt.Sprintf("Mattermost Microsoft Office plugin version: %s, "+ + "[%s](https://github.com/mattermost/%s/commit/%s), built %s\n", + r.Config.PluginVersion, + r.Config.BuildHashShort, + config.Repository, + r.Config.BuildHash, + r.Config.BuildDate) + return resp, nil +} diff --git a/server/command/view_calendar.go b/server/command/view_calendar.go new file mode 100644 index 00000000..257e459b --- /dev/null +++ b/server/command/view_calendar.go @@ -0,0 +1,27 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package command + +import ( + "encoding/json" + + "github.com/mattermost/mattermost-plugin-msoffice/server/msgraph" +) + +func (h *Handler) viewCalendar(parameters ...string) (string, error) { + user, err := h.loadRemoteUser() + if err != nil { + return "", err + } + + msgraphClient := msgraph.NewClient(h.Config, user.OAuth2Token) + cals, err := msgraphClient.GetUserCalendar("") + if err != nil { + return "", err + } + + bb, _ := json.MarshalIndent(cals, "", " ") + resp := "<><>" + string(bb) + return resp, nil +} diff --git a/server/config/config.go b/server/config/config.go new file mode 100644 index 00000000..bbe638fc --- /dev/null +++ b/server/config/config.go @@ -0,0 +1,36 @@ +package config + +// StoredConfig represents the data stored in and managed with the Mattermost +// config. +type StoredConfig struct { + OAuth2Authority string + OAuth2ClientId string + OAuth2ClientSecret string + + // Bot username + BotUserName string `json:"username"` + + // AdminUserIDs contains a comma-separated list of user IDs that are allowed + // to administer plugin functions, even if not Mattermost sysadmins. + AdminUserIDs string +} + +// Config represents the the metadata handed to all request runners (command, +// http). +type Config struct { + StoredConfig + + BuildHash string + BuildHashShort string + BuildDate string + + MattermostSiteHostname string + MattermostSiteURL string + PluginId string + PluginURL string + PluginURLPath string + PluginVersion string + + BotIconURL string + BotUserId string +} diff --git a/server/config/const.go b/server/config/const.go new file mode 100644 index 00000000..8f81c2db --- /dev/null +++ b/server/config/const.go @@ -0,0 +1,11 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package config + +const ( + ApplicationName = "Microsoft Office" + Repository = "mattermost-plugin-msoffice" + CommandTrigger = "msoffice" + GraphURL = "https://graph.microsoft.com" +) diff --git a/server/configuration.go b/server/configuration.go deleted file mode 100644 index 05aaf5be..00000000 --- a/server/configuration.go +++ /dev/null @@ -1,83 +0,0 @@ -package main - -import ( - "reflect" - - "github.com/pkg/errors" -) - -// configuration captures the plugin's external configuration as exposed in the Mattermost server -// configuration, as well as values computed from the configuration. Any public fields will be -// deserialized from the Mattermost server configuration in OnConfigurationChange. -// -// As plugins are inherently concurrent (hooks being called asynchronously), and the plugin -// configuration can change at any time, access to the configuration must be synchronized. The -// strategy used in this plugin is to guard a pointer to the configuration, and clone the entire -// struct whenever it changes. You may replace this with whatever strategy you choose. -// -// If you add non-reference types to your configuration struct, be sure to rewrite Clone as a deep -// copy appropriate for your types. -type configuration struct { -} - -// Clone shallow copies the configuration. Your implementation may require a deep copy if -// your configuration has reference types. -func (c *configuration) Clone() *configuration { - var clone = *c - return &clone -} - -// getConfiguration retrieves the active configuration under lock, making it safe to use -// concurrently. The active configuration may change underneath the client of this method, but -// the struct returned by this API call is considered immutable. -func (p *Plugin) getConfiguration() *configuration { - p.configurationLock.RLock() - defer p.configurationLock.RUnlock() - - if p.configuration == nil { - return &configuration{} - } - - return p.configuration -} - -// setConfiguration replaces the active configuration under lock. -// -// Do not call setConfiguration while holding the configurationLock, as sync.Mutex is not -// reentrant. In particular, avoid using the plugin API entirely, as this may in turn trigger a -// hook back into the plugin. If that hook attempts to acquire this lock, a deadlock may occur. -// -// This method panics if setConfiguration is called with the existing configuration. This almost -// certainly means that the configuration was modified without being cloned and may result in -// an unsafe access. -func (p *Plugin) setConfiguration(configuration *configuration) { - p.configurationLock.Lock() - defer p.configurationLock.Unlock() - - if configuration != nil && p.configuration == configuration { - // Ignore assignment if the configuration struct is empty. Go will optimize the - // allocation for same to point at the same memory address, breaking the check - // above. - if reflect.ValueOf(*configuration).NumField() == 0 { - return - } - - panic("setConfiguration called with the existing configuration") - } - - p.configuration = configuration -} - -// OnConfigurationChange is invoked when configuration changes may have been made. -func (p *Plugin) OnConfigurationChange() error { - var configuration = new(configuration) - - // Load the public configuration fields from the Mattermost server configuration. - if err := p.API.LoadPluginConfiguration(configuration); err != nil { - return errors.Wrap(err, "failed to load plugin configuration") - } - - p.setConfiguration(configuration) - - return nil -} diff --git a/server/http/api_authorized.go b/server/http/api_authorized.go new file mode 100644 index 00000000..e665e4d8 --- /dev/null +++ b/server/http/api_authorized.go @@ -0,0 +1,12 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package http + +import "net/http" + +func (h *Handler) apiGetAuthorized(w http.ResponseWriter, r *http.Request) { + // if we've made it here, we're authorized. + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"is_authorized": true}`)) +} diff --git a/server/http/http.go b/server/http/http.go new file mode 100644 index 00000000..cc53b2f9 --- /dev/null +++ b/server/http/http.go @@ -0,0 +1,89 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package http + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + + "github.com/mattermost/mattermost-server/plugin" + + "github.com/mattermost/mattermost-plugin-msoffice/server/config" + "github.com/mattermost/mattermost-plugin-msoffice/server/user" + "github.com/mattermost/mattermost-plugin-msoffice/server/utils" +) + +// Handler is an http.Handler for all plugin HTTP endpoints +type Handler struct { + Config *config.Config + UserStore user.Store + API plugin.API + BotPoster utils.BotPoster + IsAuthorizedAdmin func(userId string) (bool, error) + root *mux.Router +} + +// InitRouter initializes the router. +func (h *Handler) InitRouter() { + h.root = mux.NewRouter() + api := h.root.PathPrefix("/api/v1").Subrouter() + api.Use(authorizationRequired) + api.HandleFunc("/authorized", h.apiGetAuthorized).Methods("GET") + + oauth2 := h.root.PathPrefix("/oauth2").Subrouter() + oauth2.Use(authorizationRequired) + oauth2.HandleFunc("/connect", h.oauth2Connect).Methods("GET") + oauth2.HandleFunc("/complete", h.oauth2Complete).Methods("GET") + + h.root.Handle("{anything:.*}", http.NotFoundHandler()) + return +} + +func (h *Handler) jsonError(w http.ResponseWriter, err error) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + b, _ := json.Marshal(struct { + Error string `json:"error"` + Details string `json:"details"` + }{ + Error: "An internal error has occurred. Check app server logs for details.", + Details: err.Error(), + }) + _, _ = w.Write(b) +} + +func authorizationRequired(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + userID := r.Header.Get("Mattermost-User-ID") + if userID != "" { + next.ServeHTTP(w, r) + return + } + http.Error(w, "Not authorized", http.StatusUnauthorized) + }) +} + +func (h *Handler) adminAuthorizationRequired(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + userID := r.Header.Get("Mattermost-User-ID") + authorized, err := h.IsAuthorizedAdmin(userID) + if err != nil { + h.API.LogError("Admin authorization failed", "error", err.Error()) + http.Error(w, "Not authorized", http.StatusUnauthorized) + return + } + if authorized { + next.ServeHTTP(w, r) + return + } + http.Error(w, "Not authorized", http.StatusUnauthorized) + }) +} + +// ServeHTTP implements http.Handler +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.root.ServeHTTP(w, r) +} diff --git a/server/http/oauth2_complete.go b/server/http/oauth2_complete.go new file mode 100644 index 00000000..aec3d3ca --- /dev/null +++ b/server/http/oauth2_complete.go @@ -0,0 +1,97 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package http + +import ( + "context" + "fmt" + "log" + "net/http" + "strings" + + "github.com/mattermost/mattermost-plugin-msoffice/server/msgraph" + "github.com/mattermost/mattermost-plugin-msoffice/server/user" +) + +func (h *Handler) oauth2Complete(w http.ResponseWriter, r *http.Request) { + authedUserID := r.Header.Get("Mattermost-User-ID") + if authedUserID == "" { + http.Error(w, "Not authorized", http.StatusUnauthorized) + return + } + + ctx := context.Background() + oconf := msgraph.GetOAuth2Config(h.Config) + + code := r.URL.Query().Get("code") + if len(code) == 0 { + http.Error(w, "missing authorization code", http.StatusBadRequest) + return + } + + state := r.URL.Query().Get("state") + stateStore := user.NewOAuth2StateStore(h.API) + err := stateStore.Verify(state) + if err != nil { + http.Error(w, "missing stored state: "+err.Error(), http.StatusBadRequest) + return + } + + userID := strings.Split(state, "_")[1] + if userID != authedUserID { + http.Error(w, "Not authorized, user ID mismatch.", http.StatusUnauthorized) + return + } + + tok, err := oconf.Exchange(ctx, code) + if err != nil { + fmt.Println(err.Error()) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + msclient := msgraph.NewClient(h.Config, tok) + me, err := msclient.GetMe() + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + u := &user.User{ + PluginVersion: h.Config.PluginVersion, + OAuth2Token: tok, + Settings: &user.Settings{ + Notifications: true, + }, + } + + err = h.UserStore.Store(userID, u) + if err != nil { + http.Error(w, "Unable to connect: "+err.Error(), http.StatusInternalServerError) + return + } + + message := fmt.Sprintf("### Welcome to the Microsoft Office plugin!\n"+ + "Here is some info to prove we got you logged in\n"+ + "Name: %s \n", me.DisplayName) + h.BotPoster.PostDirect(userID, message, "custom_TODO") + + html := ` + + +
+ + + +Completed connecting to Microsoft Office. Please close this window.
+ + + ` + + w.Header().Set("Content-Type", "text/html") + w.Write([]byte(html)) +} diff --git a/server/http/oauth2_connect.go b/server/http/oauth2_connect.go new file mode 100644 index 00000000..5312907e --- /dev/null +++ b/server/http/oauth2_connect.go @@ -0,0 +1,36 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package http + +import ( + "fmt" + "net/http" + + "golang.org/x/oauth2" + + "github.com/mattermost/mattermost-server/model" + + "github.com/mattermost/mattermost-plugin-msoffice/server/msgraph" + "github.com/mattermost/mattermost-plugin-msoffice/server/user" +) + +func (h *Handler) oauth2Connect(w http.ResponseWriter, r *http.Request) { + userID := r.Header.Get("Mattermost-User-ID") + if userID == "" { + http.Error(w, "Not authorized", http.StatusUnauthorized) + return + } + + conf := msgraph.GetOAuth2Config(h.Config) + state := fmt.Sprintf("%v_%v", model.NewId()[0:15], userID) + stateStore := user.NewOAuth2StateStore(h.API) + err := stateStore.Store(state) + if err != nil { + h.jsonError(w, err) + return + } + + url := conf.AuthCodeURL(state, oauth2.AccessTypeOffline) + http.Redirect(w, r, url, http.StatusFound) +} diff --git a/server/kvstore/hashed_key.go b/server/kvstore/hashed_key.go new file mode 100644 index 00000000..13d7a15c --- /dev/null +++ b/server/kvstore/hashed_key.go @@ -0,0 +1,45 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package kvstore + +import ( + "crypto/md5" + "fmt" +) + +type hashedKeyStore struct { + store KVStore + prefix string +} + +var _ KVStore = (*hashedKeyStore)(nil) + +func NewHashedKeyStore(s KVStore, prefix string) KVStore { + return &hashedKeyStore{ + store: s, + prefix: prefix, + } +} + +func (s hashedKeyStore) Load(key string) ([]byte, error) { + return s.store.Load(hashKey(s.prefix, key)) +} + +func (s hashedKeyStore) Store(key string, data []byte) error { + return s.store.Store(hashKey(s.prefix, key), data) +} + +func (s hashedKeyStore) Delete(key string) error { + return s.store.Delete(hashKey(s.prefix, key)) +} + +func hashKey(prefix, hashableKey string) string { + if hashableKey == "" { + return prefix + } + + h := md5.New() + _, _ = h.Write([]byte(hashableKey)) + return fmt.Sprintf("%s%x", prefix, h.Sum(nil)) +} diff --git a/server/kvstore/hashed_key_test.go b/server/kvstore/hashed_key_test.go new file mode 100644 index 00000000..2429e952 --- /dev/null +++ b/server/kvstore/hashed_key_test.go @@ -0,0 +1,33 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package kvstore + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_hashKey(t *testing.T) { + type args struct { + prefix string + hashableKey string + } + tests := []struct { + name string + args args + want string + }{ + {"empty", args{"", ""}, ""}, + {"value", args{"", "https://mmtest.mattermost.com"}, "53d1d6fa60f26d84e2087f61d535d073"}, + {"prefix", args{"abc_", ""}, "abc_"}, + {"prefix value", args{"abc_", "123"}, "abc_202cb962ac59075b964b07152d234b70"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := hashKey(tt.args.prefix, tt.args.hashableKey) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/server/kvstore/keys.go b/server/kvstore/keys.go new file mode 100644 index 00000000..d17e64ae --- /dev/null +++ b/server/kvstore/keys.go @@ -0,0 +1,4 @@ +package kvstore + +// TODO find a better way of documenting all DB keys in one place. +const () diff --git a/server/kvstore/kvstore.go b/server/kvstore/kvstore.go new file mode 100644 index 00000000..bbe8ad83 --- /dev/null +++ b/server/kvstore/kvstore.go @@ -0,0 +1,57 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package kvstore + +import ( + "encoding/json" + "errors" +) + +type KVStore interface { + Load(key string) ([]byte, error) + Store(key string, data []byte) error + Delete(key string) error +} + +var ErrNotFound = errors.New("not found") + +func Ensure(s KVStore, key string, newValue []byte) ([]byte, error) { + value, err := s.Load(key) + switch err { + case nil: + return value, nil + case ErrNotFound: + break + default: + return nil, err + } + + err = s.Store(key, newValue) + if err != nil { + return nil, err + } + + // Load again in case we lost the race to another server + value, err = s.Load(key) + if err != nil { + return newValue, nil + } + return value, nil +} + +func LoadJSON(s KVStore, key string, v interface{}) (returnErr error) { + data, err := s.Load(key) + if err != nil { + return err + } + return json.Unmarshal(data, v) +} + +func StoreJSON(s KVStore, key string, v interface{}) (returnErr error) { + data, err := json.Marshal(v) + if err != nil { + return err + } + return s.Store(key, data) +} diff --git a/server/kvstore/ots.go b/server/kvstore/ots.go new file mode 100644 index 00000000..30453e96 --- /dev/null +++ b/server/kvstore/ots.go @@ -0,0 +1,37 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package kvstore + +import ( + "time" + + "github.com/mattermost/mattermost-server/plugin" +) + +// OneTimeStore is a KV store that deletes each record after the first load, +type OneTimeStore KVStore + +type ots struct { + KVStore +} + +func NewOneTimePluginStore(api plugin.API, ttl time.Duration) OneTimeStore { + return &ots{ + KVStore: NewPluginStoreWithExpiry(api, ttl), + } +} + +func NewOneTimeStore(kv KVStore) OneTimeStore { + return &ots{ + KVStore: kv, + } +} + +func (s ots) Load(key string) (data []byte, returnErr error) { + data, err := s.KVStore.Load(key) + if len(data) == 0 { + _ = s.Delete(key) + } + return data, err +} diff --git a/server/kvstore/plugin_store.go b/server/kvstore/plugin_store.go new file mode 100644 index 00000000..c417810b --- /dev/null +++ b/server/kvstore/plugin_store.go @@ -0,0 +1,63 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package kvstore + +import ( + "time" + + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/plugin" + + "github.com/pkg/errors" +) + +type pluginStore struct { + api plugin.API + ttlSeconds int64 +} + +var _ KVStore = (*pluginStore)(nil) + +func NewPluginStore(api plugin.API) KVStore { + return NewPluginStoreWithExpiry(api, 0) +} + +func NewPluginStoreWithExpiry(api plugin.API, ttl time.Duration) KVStore { + return &pluginStore{ + api: api, + ttlSeconds: (int64)(ttl / time.Second), + } +} + +func (s pluginStore) Load(key string) ([]byte, error) { + data, appErr := s.api.KVGet(key) + if appErr != nil { + return nil, errors.WithMessage(appErr, "failed plugin KVGet") + } + if data == nil { + return nil, ErrNotFound + } + return data, nil +} + +func (s pluginStore) Store(key string, data []byte) error { + var appErr *model.AppError + if s.ttlSeconds > 0 { + appErr = s.api.KVSetWithExpiry(key, data, s.ttlSeconds) + } else { + appErr = s.api.KVSet(key, data) + } + if appErr != nil { + return errors.WithMessagef(appErr, "failed plugin KVSet (ttl: %vs) %q", s.ttlSeconds, key) + } + return nil +} + +func (s pluginStore) Delete(key string) error { + appErr := s.api.KVDelete(key) + if appErr != nil { + return errors.WithMessagef(appErr, "failed plugin KVdelete %q", key) + } + return nil +} diff --git a/server/main.go b/server/main.go index 69cc896e..d548169a 100644 --- a/server/main.go +++ b/server/main.go @@ -1,9 +1,24 @@ package main import ( - "github.com/mattermost/mattermost-server/plugin" + mattermost "github.com/mattermost/mattermost-server/plugin" + + "github.com/mattermost/mattermost-plugin-msoffice/server/config" + msoffice "github.com/mattermost/mattermost-plugin-msoffice/server/plugin" ) +var BuildHash string +var BuildHashShort string +var BuildDate string + func main() { - plugin.ClientMain(&Plugin{}) + mattermost.ClientMain( + msoffice.NewWithConfig( + &config.Config{ + PluginId: manifest.ID, + PluginVersion: manifest.Version, + BuildHash: BuildHash, + BuildHashShort: BuildHashShort, + BuildDate: BuildHash, + })) } diff --git a/server/manifest.go b/server/manifest.go index 471775b4..d4a8079e 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -6,6 +6,6 @@ var manifest = struct { ID string Version string }{ - ID: "com.mattermost.plugin-starter-template", + ID: "com.mattermost.msoffice", Version: "0.1.0", } diff --git a/server/msgraph/client.go b/server/msgraph/client.go new file mode 100644 index 00000000..42a1ac6f --- /dev/null +++ b/server/msgraph/client.go @@ -0,0 +1,54 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package msgraph + +import ( + "fmt" + + graph "github.com/jkrecek/msgraph-go" + "golang.org/x/oauth2" + + "github.com/mattermost/mattermost-plugin-msoffice/server/config" +) + +const OAuth2RedirectPath = "/oauth2/complete" + +const ( + authURLEndpoint = "https://login.microsoftonline.com/%s/oauth2/v2.0/authorize" + tokenURLEndpoint = "https://login.microsoftonline.com/%s/oauth2/v2.0/token" +) + +type Client interface { + GetMe() (*graph.Me, error) + GetUserCalendar(remoteUserId string) ([]*graph.Calendar, error) +} + +type client struct { + graph *graph.Client +} + +// NewClient creates a new Client from a Token. +func NewClient(conf *config.Config, token *oauth2.Token) Client { + c := &client{ + graph: graph.NewClient(GetOAuth2Config(conf), token), + } + return c +} + +func GetOAuth2Config(conf *config.Config) *oauth2.Config { + return &oauth2.Config{ + ClientID: conf.OAuth2ClientId, + ClientSecret: conf.OAuth2ClientSecret, + RedirectURL: conf.PluginURL + OAuth2RedirectPath, + Scopes: []string{ + "User.Read", + "Calendars.ReadWrite", + "Calendars.ReadWrite.Shared", + }, + Endpoint: oauth2.Endpoint{ + AuthURL: fmt.Sprintf(authURLEndpoint, conf.OAuth2Authority), + TokenURL: fmt.Sprintf(tokenURLEndpoint, conf.OAuth2Authority), + }, + } +} diff --git a/server/msgraph/get_me.go b/server/msgraph/get_me.go new file mode 100644 index 00000000..a44d1e8f --- /dev/null +++ b/server/msgraph/get_me.go @@ -0,0 +1,10 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package msgraph + +import graph "github.com/jkrecek/msgraph-go" + +func (c *client) GetMe() (*graph.Me, error) { + return c.graph.GetMe() +} diff --git a/server/msgraph/get_user_calendar.go b/server/msgraph/get_user_calendar.go new file mode 100644 index 00000000..6b14a4c4 --- /dev/null +++ b/server/msgraph/get_user_calendar.go @@ -0,0 +1,10 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package msgraph + +import graph "github.com/jkrecek/msgraph-go" + +func (c *client) GetUserCalendar(remoteUserId string) ([]*graph.Calendar, error) { + return c.graph.GetMeCalendar() +} diff --git a/server/plugin.go b/server/plugin.go deleted file mode 100644 index db89645c..00000000 --- a/server/plugin.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "sync" - - "github.com/mattermost/mattermost-server/plugin" -) - -// Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes. -type Plugin struct { - plugin.MattermostPlugin - - // configurationLock synchronizes access to the configuration. - configurationLock sync.RWMutex - - // configuration is the active plugin configuration. Consult getConfiguration and - // setConfiguration for usage. - configuration *configuration -} - -// ServeHTTP demonstrates a plugin that handles HTTP requests by greeting the world. -func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "Hello, world!") -} - -// See https://developers.mattermost.com/extend/plugins/server/reference/ diff --git a/server/plugin/authorization.go b/server/plugin/authorization.go new file mode 100644 index 00000000..49524226 --- /dev/null +++ b/server/plugin/authorization.go @@ -0,0 +1,30 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package plugin + +import ( + "strings" +) + +// isAuthorized returns true if the user is authorized to use the workflow plugin's admin-level APIs/commands. +func (p *Plugin) IsAuthorizedAdmin(mattermostID string) (bool, error) { + user, err := p.API.GetUser(mattermostID) + if err != nil { + return false, err + } + if strings.Contains(user.Roles, "system_admin") || p.userAuthorized(mattermostID) { + return true, nil + } + return false, nil +} + +func (p *Plugin) userAuthorized(mattermostID string) bool { + list := strings.Split(p.getConfig().AdminUserIDs, ",") + for _, u := range list { + if mattermostID == strings.TrimSpace(u) { + return true + } + } + return false +} diff --git a/server/plugin/plugin.go b/server/plugin/plugin.go new file mode 100644 index 00000000..74c441aa --- /dev/null +++ b/server/plugin/plugin.go @@ -0,0 +1,206 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package plugin + +import ( + "fmt" + gohttp "net/http" + "net/url" + "os" + "path/filepath" + "strings" + "sync" + "text/template" + + "github.com/pkg/errors" + + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/plugin" + + "github.com/mattermost/mattermost-plugin-msoffice/server/command" + "github.com/mattermost/mattermost-plugin-msoffice/server/config" + "github.com/mattermost/mattermost-plugin-msoffice/server/http" + "github.com/mattermost/mattermost-plugin-msoffice/server/kvstore" + "github.com/mattermost/mattermost-plugin-msoffice/server/user" + "github.com/mattermost/mattermost-plugin-msoffice/server/utils" +) + +type Plugin struct { + plugin.MattermostPlugin + configLock *sync.RWMutex + config *config.Config + httpHandler *http.Handler + + KVStore kvstore.KVStore + UserStore user.Store + OAuth2StateStore user.OAuth2StateStore + + Templates map[string]*template.Template +} + +func NewWithConfig(conf *config.Config) *Plugin { + return &Plugin{ + configLock: &sync.RWMutex{}, + config: conf, + } +} + +func (p *Plugin) OnActivate() error { + err := p.loadTemplates() + if err != nil { + return err + } + + kv := kvstore.NewPluginStore(p.API) + p.KVStore = kv + p.UserStore = user.NewStore(kv) + p.OAuth2StateStore = user.NewOAuth2StateStore(p.API) + + command.Register(p.API.RegisterCommand) + p.httpHandler = p.newHTTPHandler(&(*p.config)) + + p.API.LogInfo(p.config.PluginId + " activated") + return nil +} + +// OnConfigurationChange is invoked when configuration changes may have been made. +func (p *Plugin) OnConfigurationChange() error { + conf := p.getConfig() + oldStored := conf.StoredConfig + newStored := config.StoredConfig{} + err := p.API.LoadPluginConfiguration(&newStored) + if err != nil { + return errors.WithMessage(err, "failed to load plugin configuration") + } + + if newStored.OAuth2Authority == "" || + newStored.OAuth2ClientId == "" || + newStored.OAuth2ClientSecret == "" { + return errors.WithMessage(err, "failed to configure: OAuth2 credentials to be set in the config") + } + + botUserId := conf.BotUserId + if newStored.BotUserName != oldStored.BotUserName { + user, appErr := p.API.GetUserByUsername(newStored.BotUserName) + if appErr != nil { + return errors.WithMessage(appErr, fmt.Sprintf("unable to load user %s", newStored.BotUserName)) + } + botUserId = user.Id + } + + mattermostSiteURL := p.API.GetConfig().ServiceSettings.SiteURL + if mattermostSiteURL == nil { + return errors.New("plugin requires Mattermost Site URL to be set") + } + mattermostURL, err := url.Parse(*mattermostSiteURL) + if err != nil { + return err + } + pluginURLPath := "/plugins/" + conf.PluginId + pluginURL := strings.TrimRight(*mattermostSiteURL, "/") + pluginURLPath + + p.updateConfig(func(c *config.Config) { + c.StoredConfig = newStored + + // TODO Update c.BotIconURL = "" + c.BotUserId = botUserId + c.MattermostSiteURL = *mattermostSiteURL + c.MattermostSiteHostname = mattermostURL.Hostname() + c.PluginURL = pluginURL + c.PluginURLPath = pluginURLPath + + p.httpHandler = p.newHTTPHandler(&(*c)) + }) + + return nil +} + +func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { + conf := p.getConfig() + poster := utils.NewBotPoster(conf, p.API) + + h := command.Handler{ + Config: conf, + UserStore: p.UserStore, + API: p.API, + BotPoster: utils.NewBotPoster(conf, p.API), + IsAuthorizedAdmin: p.IsAuthorizedAdmin, + Context: c, + Args: args, + ChannelId: args.ChannelId, + MattermostUserId: args.UserId, + } + out, err := h.Handle() + if err != nil { + p.API.LogError(err.Error()) + return nil, model.NewAppError("msofficeplugin.ExecuteCommand", "Unable to execute command.", nil, err.Error(), gohttp.StatusInternalServerError) + } + poster.PostEphemeral(args.UserId, args.ChannelId, out) + + return &model.CommandResponse{}, nil +} + +func (p *Plugin) ServeHTTP(pc *plugin.Context, w gohttp.ResponseWriter, r *gohttp.Request) { + p.configLock.RLock() + handler := p.httpHandler + p.configLock.RUnlock() + + handler.ServeHTTP(w, r) +} + +func (p *Plugin) newHTTPHandler(conf *config.Config) *http.Handler { + h := &http.Handler{ + Config: conf, + UserStore: p.UserStore, + API: p.API, + BotPoster: utils.NewBotPoster(conf, p.API), + IsAuthorizedAdmin: p.IsAuthorizedAdmin, + } + h.InitRouter() + return h +} + +func (p *Plugin) getConfig() *config.Config { + p.configLock.RLock() + defer p.configLock.RUnlock() + return &(*p.config) +} + +func (p *Plugin) updateConfig(f func(*config.Config)) config.Config { + p.configLock.Lock() + defer p.configLock.Unlock() + + f(p.config) + return *p.config +} + +func (p *Plugin) loadTemplates() error { + if p.Templates != nil { + return nil + } + templatesPath := filepath.Join(*(p.API.GetConfig().PluginSettings.Directory), + p.config.PluginId, "server", "dist", "templates") + + templates := make(map[string]*template.Template) + err := filepath.Walk(templatesPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + template, err := template.ParseFiles(path) + if err != nil { + return nil + } + key := path[len(templatesPath):] + templates[key] = template + return nil + }) + if err != nil { + return errors.WithMessage(err, "OnActivate/loadTemplates failed") + } + p.Templates = templates + return nil +} diff --git a/server/plugin_test.go b/server/plugin_test.go deleted file mode 100644 index 9b7aa9cd..00000000 --- a/server/plugin_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestServeHTTP(t *testing.T) { - assert := assert.New(t) - plugin := Plugin{} - w := httptest.NewRecorder() - r := httptest.NewRequest(http.MethodGet, "/", nil) - - plugin.ServeHTTP(nil, w, r) - - result := w.Result() - assert.NotNil(result) - bodyBytes, err := ioutil.ReadAll(result.Body) - assert.Nil(err) - bodyString := string(bodyBytes) - - assert.Equal("Hello, world!", bodyString) -} diff --git a/server/user/oauth2_store.go b/server/user/oauth2_store.go new file mode 100644 index 00000000..e33776ea --- /dev/null +++ b/server/user/oauth2_store.go @@ -0,0 +1,46 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package user + +import ( + "errors" + "time" + + "github.com/mattermost/mattermost-server/plugin" + + "github.com/mattermost/mattermost-plugin-msoffice/server/kvstore" +) + +const OAuth2KeyPrefix = "user_" +const OAuth2KeyExpiration = 15 * time.Minute + +type OAuth2StateStore interface { + Verify(state string) error + Store(state string) error +} + +type oauth2StateStore struct { + kv kvstore.KVStore +} + +func NewOAuth2StateStore(api plugin.API) OAuth2StateStore { + return &oauth2StateStore{ + kv: kvstore.NewHashedKeyStore(kvstore.NewOneTimePluginStore(api, OAuth2KeyExpiration), OAuth2KeyPrefix), + } +} + +func (s *oauth2StateStore) Verify(state string) error { + data, err := s.kv.Load(state) + if err != nil { + return err + } + if string(data) != state { + return errors.New("authentication attempt expired, please try again.") + } + return nil +} + +func (s *oauth2StateStore) Store(state string) error { + return s.kv.Store(state, []byte(state)) +} diff --git a/server/user/user.go b/server/user/user.go new file mode 100644 index 00000000..6fd2eb68 --- /dev/null +++ b/server/user/user.go @@ -0,0 +1,30 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package user + +import ( + "fmt" + + graph "github.com/jkrecek/msgraph-go" + "golang.org/x/oauth2" +) + +type User struct { + graph.Me + PluginVersion string + OAuth2Token *oauth2.Token `json:",omitempty"` + Settings *Settings +} + +type Settings struct { + Notifications bool `json:"notifications"` +} + +func (settings Settings) String() string { + notifications := "off" + if settings.Notifications { + notifications = "on" + } + return fmt.Sprintf("\tNotifications: %s", notifications) +} diff --git a/server/user/user_store.go b/server/user/user_store.go new file mode 100644 index 00000000..dc5ae350 --- /dev/null +++ b/server/user/user_store.go @@ -0,0 +1,71 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package user + +import ( + "github.com/mattermost/mattermost-plugin-msoffice/server/kvstore" +) + +const UserKeyPrefix = "user_" + +type Store interface { + LoadRemoteUser(mattermostUserId string, ref interface{}) error + LoadMattermostUserId(remoteUserId string) (string, error) + Store(mattermostUserId string, user *User) error + Delete(mattermostUserId string) error +} + +type store struct { + kv kvstore.KVStore +} + +func NewStore(s kvstore.KVStore) Store { + return &store{ + kv: kvstore.NewHashedKeyStore(s, UserKeyPrefix), + } +} + +func (s *store) LoadRemoteUser(mattermostUserId string, ref interface{}) error { + return kvstore.LoadJSON(s.kv, mattermostUserId, ref) +} + +func (s *store) LoadMattermostUserId(remoteUserId string) (string, error) { + data, err := s.kv.Load(remoteUserId) + if err != nil { + return "", err + } + return string(data), nil +} + +func (s *store) Store(mattermostUserId string, user *User) error { + err := kvstore.StoreJSON(s.kv, mattermostUserId, user) + if err != nil { + return err + } + + err = s.kv.Store(user.Me.Id, []byte(mattermostUserId)) + if err != nil { + _ = s.kv.Delete(mattermostUserId) + return err + } + + return nil +} + +func (s *store) Delete(mattermostUserId string) error { + u := User{} + err := s.LoadRemoteUser(mattermostUserId, &u) + if err != nil { + return err + } + err = s.kv.Delete(mattermostUserId) + if err != nil { + return err + } + err = s.kv.Delete(u.Me.Id) + if err != nil { + return err + } + return nil +} diff --git a/server/utils/bot_poster.go b/server/utils/bot_poster.go new file mode 100644 index 00000000..a332954f --- /dev/null +++ b/server/utils/bot_poster.go @@ -0,0 +1,60 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package utils + +import ( + "github.com/mattermost/mattermost-server/mlog" + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/plugin" + + "github.com/mattermost/mattermost-plugin-msoffice/server/config" +) + +type BotPoster interface { + PostDirect(userID, message, postType string) error + PostEphemeral(userID, channelId, message string) +} + +type botPoster struct { + config *config.Config + API plugin.API +} + +func NewBotPoster(conf *config.Config, api plugin.API) BotPoster { + return &botPoster{ + config: conf, + API: api, + } +} + +func (poster *botPoster) PostDirect(userID, message, postType string) error { + channel, err := poster.API.GetDirectChannel(userID, poster.config.BotUserId) + if err != nil { + poster.API.LogInfo("Couldn't get bot's DM channel", "user_id", userID) + return err + } + + post := &model.Post{ + UserId: poster.config.BotUserId, + ChannelId: channel.Id, + Message: message, + Type: postType, + } + + if _, err := poster.API.CreatePost(post); err != nil { + mlog.Error(err.Error()) + return err + } + + return nil +} + +func (poster *botPoster) PostEphemeral(userId, channelId, message string) { + post := &model.Post{ + UserId: poster.config.BotUserId, + ChannelId: channelId, + Message: message, + } + _ = poster.API.SendEphemeralPost(userId, post) +} diff --git a/server/utils/byte_size.go b/server/utils/byte_size.go new file mode 100644 index 00000000..2362eb05 --- /dev/null +++ b/server/utils/byte_size.go @@ -0,0 +1,89 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package utils + +import ( + "math" + "strconv" + "strings" +) + +type ByteSize int64 + +const sizeB = ByteSize(1) +const sizeKb = 1024 * sizeB +const sizeMb = 1024 * sizeKb +const sizeGb = 1024 * sizeMb +const sizeTb = 1024 * sizeGb + +var sizeUnits = []ByteSize{sizeTb, sizeGb, sizeMb, sizeKb, sizeB} +var sizeSuffixes = []string{"Tb", "Gb", "Mb", "Kb", "b"} + +func (size ByteSize) String() string { + if size == 0 { + return "0" + } + + withCommas := func(in string) string { + out := "" + for len(in) > 3 { + out = "," + in[len(in)-3:] + out + in = in[:len(in)-3] + } + out = in + out + return out + } + + for i, u := range sizeUnits { + if size < u { + continue + } + if u == sizeB { + return withCommas(strconv.FormatUint(uint64(size), 10)) + sizeSuffixes[i] + } + + if size > math.MaxInt64/10 { + return "n/a" + } + + s := strconv.FormatUint(uint64((size*10+u/2)/u), 10) + l := len(s) + switch { + case l < 2: + return "n/a" + case s[l-1] == '0': + return withCommas(s[:l-1]) + sizeSuffixes[i] + default: + return withCommas(s[:l-1]) + "." + s[l-1:] + sizeSuffixes[i] + } + } + return "n/a" +} + +func ParseByteSize(str string) (ByteSize, error) { + u := sizeB + str = strings.ToLower(str) + for i, s := range sizeSuffixes { + if strings.HasSuffix(str, strings.ToLower(s)) { + str = str[:len(str)-len(s)] + u = sizeUnits[i] + break + } + } + + str = strings.ReplaceAll(str, ",", "") + n, err := strconv.ParseInt(str, 10, 64) + if err == nil { + return ByteSize(n) * u, nil + } + numerr := err.(*strconv.NumError) + if numerr.Err != strconv.ErrSyntax { + return 0, err + } + fl, err := strconv.ParseFloat(str, 64) + if err != nil { + return 0, err + } + return ByteSize(fl * float64(u)), nil +} diff --git a/server/utils/limited_readcloser.go b/server/utils/limited_readcloser.go new file mode 100644 index 00000000..b7acb48a --- /dev/null +++ b/server/utils/limited_readcloser.go @@ -0,0 +1,40 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package utils + +import ( + "io" +) + +type LimitReadCloser struct { + ReadCloser io.ReadCloser + TotalRead ByteSize + Limit ByteSize + OnClose func(*LimitReadCloser) error +} + +func (r *LimitReadCloser) Read(data []byte) (int, error) { + if r.Limit >= 0 { + remain := r.Limit - r.TotalRead + if remain <= 0 { + return 0, io.EOF + } + if len(data) > int(remain) { + data = data[0:remain] + } + } + n, err := r.ReadCloser.Read(data) + r.TotalRead += ByteSize(n) + return n, err +} + +func (r *LimitReadCloser) Close() error { + if r.OnClose != nil { + err := r.OnClose(r) + if err != nil { + return err + } + } + return r.ReadCloser.Close() +} diff --git a/server/utils/limited_readcloser_test.go b/server/utils/limited_readcloser_test.go new file mode 100644 index 00000000..1ba0139e --- /dev/null +++ b/server/utils/limited_readcloser_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package utils + +import ( + "io" + "io/ioutil" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLimitReadCloser(t *testing.T) { + inner := ioutil.NopCloser(strings.NewReader("01234567890")) + + totalRead := ByteSize(0) + r := &LimitReadCloser{ + ReadCloser: inner, + Limit: 8, + OnClose: func(rr *LimitReadCloser) error { + totalRead = rr.TotalRead + return io.EOF + }, + } + data := make([]byte, 10) + + n, err := r.Read(data[0:4]) + require.Nil(t, err) + require.Equal(t, 4, n) + require.Equal(t, "0123", string(data[0:4])) + + n, err = r.Read(data[0:5]) + require.Nil(t, err) + // Note, truncated to 4, total 8 + require.Equal(t, 4, n) + require.Equal(t, "4567", string(data[0:4])) + + n, err = r.Read(data[0:1]) + require.Equal(t, io.EOF, err) + require.Equal(t, 0, n) + + err = r.Close() + require.Equal(t, io.EOF, err) + require.Equal(t, ByteSize(8), totalRead) +} diff --git a/server/utils/utils.go b/server/utils/utils.go new file mode 100644 index 00000000..c6ea599d --- /dev/null +++ b/server/utils/utils.go @@ -0,0 +1,52 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package utils + +import ( + "net/url" + "path" + "strings" + + "github.com/pkg/errors" +) + +func NormalizeInstallURL(mattermostSiteURL, remoteURL string) (string, error) { + u, err := url.Parse(remoteURL) + if err != nil { + return "", err + } + if u.Host == "" { + ss := strings.Split(u.Path, "/") + if len(ss) > 0 && ss[0] != "" { + u.Host = ss[0] + u.Path = path.Join(ss[1:]...) + } + u, err = url.Parse(u.String()) + if err != nil { + return "", err + } + } + if u.Host == "" { + return "", errors.Errorf("Invalid URL, no hostname: %q", remoteURL) + } + if u.Scheme == "" { + u.Scheme = "https" + } + + remoteURL = strings.TrimSuffix(u.String(), "/") + if remoteURL == strings.TrimSuffix(mattermostSiteURL, "/") { + return "", errors.Errorf("%s is the Mattermost site URL. Please use the remote application's URL.", remoteURL) + } + + return remoteURL, nil +} + +// Reference: https://gobyexample.com/collection-functions +func Map(vs []string, f func(string) string) []string { + vsm := make([]string, len(vs)) + for i, v := range vs { + vsm[i] = f(v) + } + return vsm +} diff --git a/server/utils/utils_test.go b/server/utils/utils_test.go new file mode 100644 index 00000000..79032df6 --- /dev/null +++ b/server/utils/utils_test.go @@ -0,0 +1,140 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +package utils + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNormalizeInstallURL(t *testing.T) { + for _, tc := range []struct { + in, siteURL, out, err string + }{ + // Happy + {"http://mmtest.somedomain.net", "", "http://mmtest.somedomain.net", ""}, + {"https://mmtest.somedomain.net", "", "https://mmtest.somedomain.net", ""}, + {"some://mmtest.somedomain.net", "", "some://mmtest.somedomain.net", ""}, + {"mmtest.somedomain.net", "", "https://mmtest.somedomain.net", ""}, + {"mmtest.somedomain.net/", "", "https://mmtest.somedomain.net", ""}, + {"mmtest.somedomain.net/abc", "", "https://mmtest.somedomain.net/abc", ""}, + {"mmtest.somedomain.net/abc/", "", "https://mmtest.somedomain.net/abc", ""}, + {"mmtest", "", "https://mmtest", ""}, + {"mmtest/", "", "https://mmtest", ""}, + {"//xyz.com", "", "https://xyz.com", ""}, + {"//xyz.com/", "", "https://xyz.com", ""}, + + // Errors + {"[jdsh", "", "", + `parse //[jdsh: missing ']' in host`}, + {"/mmtest", "", "", + `Invalid URL, no hostname: "/mmtest"`}, + {"/mmtest/", "", "", + `Invalid URL, no hostname: "/mmtest/"`}, + {"http:/mmtest/", "", "", + `Invalid URL, no hostname: "http:/mmtest/"`}, + {"hƒƒp://xyz.com", "", "", + `parse hƒƒp://xyz.com: first path segment in URL cannot contain colon`}, + {"https://mattermost.site.url", "https://mattermost.site.url/", "", + "https://mattermost.site.url is the Mattermost site URL. Please use the remote application's URL."}, + } { + t.Run(tc.in, func(t *testing.T) { + out, err := NormalizeInstallURL(tc.siteURL, tc.in) + require.Equal(t, tc.out, out) + errTxt := "" + if err != nil { + errTxt = err.Error() + } + require.Equal(t, tc.err, errTxt) + }) + } +} + +func TestParseByteSize(t *testing.T) { + tests := []struct { + str string + want ByteSize + wantErr bool + }{ + // Happy path + {"1234567890123456789", 1234567890123456789, false}, + {",,,,1,2,3,4,5,6,,,7,8,9,0,1,2,3,4,5,6,7,8,9,,", 1234567890123456789, false}, + {"1,234,567,890,123,456,789", 1234567890123456789, false}, + {"1234567890123456789b", 1234567890123456789, false}, + {"4", 4, false}, + {"4B", 4, false}, + {"1234b", 1234, false}, + {"1234.0b", 1234, false}, + {"1Kb", 1024, false}, + {"12kb", 12 * 1024, false}, + {"1.23Kb", 1259, false}, + {"1234.0kb", 1263616, false}, + {"1234Mb", 1293942784, false}, + {"1.234Mb", 1293942, false}, + {"1234Gb", 1324997410816, false}, + {"1.234Gb", 1324997410, false}, + {"1234Tb", 1356797348675584, false}, + {"1.234tb", 1356797348675, false}, + + // Errors + {"AA", 0, true}, + {"1..00kb", 0, true}, + {" 1.00b", 0, true}, + {"1AA", 0, true}, + {"1.0AA", 0, true}, + {"1/2", 0, true}, + {"0x10", 0, true}, + {"88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888", 0, true}, + } + for _, tt := range tests { + t.Run(tt.str, func(t *testing.T) { + got, err := ParseByteSize(tt.str) + if tt.wantErr { + require.NotNil(t, err) + } else { + require.Nil(t, err) + } + assert.Equal(t, got, tt.want) + }) + } +} + +func TestByteSizeString(t *testing.T) { + tests := []struct { + n ByteSize + want string + }{ + {0, "0"}, + {1, "1b"}, + {999, "999b"}, + {1000, "1,000b"}, + {1023, "1,023b"}, + {1024, "1Kb"}, + {12345, "12.1Kb"}, + {12851, "12.5Kb"}, // 12.54980 + {12852, "12.6Kb"}, // 12.55078 + {123456, "120.6Kb"}, + {1234567, "1.2Mb"}, + {12345678, "11.8Mb"}, + {123456789, "117.7Mb"}, + {1234567890, "1.1Gb"}, + {12345678900, "11.5Gb"}, + {123456789000, "115Gb"}, + {1234567890000, "1.1Tb"}, + {12345678900000, "11.2Tb"}, + {123456789000000, "112.3Tb"}, + {1234567890000000, "1,122.8Tb"}, + {12345678900000000, "11,228.3Tb"}, + {123456789000000000, "112,283.3Tb"}, + {1234567890000000000, "n/a"}, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("%d", tt.n), func(t *testing.T) { + assert.Equal(t, tt.want, tt.n.String()) + }) + } +} diff --git a/webapp/src/manifest.js b/webapp/src/manifest.js index 52d1e5b5..0dc1db11 100644 --- a/webapp/src/manifest.js +++ b/webapp/src/manifest.js @@ -1,4 +1,4 @@ // This file is automatically generated. Do not modify it manually. -export const id = 'com.mattermost.plugin-starter-template'; +export const id = 'com.mattermost.msoffice'; export const version = '0.1.0';