diff --git a/docker/Dockerfile b/docker/Dockerfile index 7fb2d0ba4..78af58a6f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -14,7 +14,7 @@ # ORC_USER (default: orc_server_user): username used to login to orchestrator backend MySQL server # ORC_PASSWORD (default: orc_server_password): password used to login to orchestrator backend MySQL server -FROM golang:1.16.6-alpine3.14 as build +FROM golang:1.18-alpine3.17 as build ENV GOPATH=/tmp/go @@ -28,7 +28,7 @@ RUN rsync -av $(find /tmp/orchestrator-release -type d -name orchestrator -maxde RUN rsync -av $(find /tmp/orchestrator-release -type d -name orchestrator-client -maxdepth 2)/ / RUN cp conf/orchestrator-sample-sqlite.conf.json /etc/orchestrator.conf.json -FROM alpine:3.14 +FROM alpine:3.17 RUN apk --no-cache add bash curl jq diff --git a/docs/README.md b/docs/README.md index 7cf7c5379..32666729b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -42,6 +42,7 @@ #### Various - [Security](security.md) + - [Azure](azure.md) - [SSL and TLS](ssl-and-tls.md) - [Pseudo GTID](pseudo-gtid.md): refactoring and high availability without using GTID. - [Agents](agents.md) diff --git a/docs/azure.md b/docs/azure.md new file mode 100644 index 000000000..2b2c09a3f --- /dev/null +++ b/docs/azure.md @@ -0,0 +1,32 @@ +# Security + +## Microsoft Azure AD Authentication + +### 1. Create the Azure Application + +First of all, you need to register an application on Azure. + +This must be done from [https://portal.azure.com/](https://portal.azure.com/) + +You can follow the Microsoft documentation [Quickstart: Register an application with the Microsoft identity platform](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) if you don't know how to do this. + +The only important point, is for the "Platform Configurations", you need to set it as "Mobile and desktop applications" with the redirect URI as "http://localhost". + + +### 2. Configuration in `Orchestrator` + +Add the following to `orchestrator`'s configuration file: + + "AuthenticationMethod": "azure", + "AzureClientID": "", + "AzureTenantID": "", + "AzureGraphUserScope": "User.Read", + "AzureRedirectURL": "http://localhost", + "AzureApplicationName": "", + "AzureApplicationID": "", + "AzureAdminRole": "", + +![Azure App Registration](images/azure-app-registration.png) +![Azure Platform Configuration](images/azure-platform-configurations.png) +![Azure App Roles](images/azure-app-roles.png) +![Azure Users and Groups](images/azure-users-and-groups.png) \ No newline at end of file diff --git a/docs/images/azure-app-registration.png b/docs/images/azure-app-registration.png new file mode 100644 index 000000000..7a439b4af Binary files /dev/null and b/docs/images/azure-app-registration.png differ diff --git a/docs/images/azure-app-roles.png b/docs/images/azure-app-roles.png new file mode 100644 index 000000000..eef2fa6f6 Binary files /dev/null and b/docs/images/azure-app-roles.png differ diff --git a/docs/images/azure-platform-configurations.png b/docs/images/azure-platform-configurations.png new file mode 100644 index 000000000..40c22759b Binary files /dev/null and b/docs/images/azure-platform-configurations.png differ diff --git a/docs/images/azure-users-and-groups.png b/docs/images/azure-users-and-groups.png new file mode 100644 index 000000000..0547a1dcf Binary files /dev/null and b/docs/images/azure-users-and-groups.png differ diff --git a/docs/security.md b/docs/security.md index 9dd856bcd..d01993be6 100644 --- a/docs/security.md +++ b/docs/security.md @@ -54,6 +54,11 @@ When operating in HTTP mode (API or Web), access to `orchestrator` may be restri "wallace", "gromit", "shaun" ], +* _Microsoft Azure AD authentication_ + + Check the specific documentation: [Azure](azure.md) + + Or, regardless, you may turn the entire `orchestrator` process to be read only via: diff --git a/docs/toc.md b/docs/toc.md index 7cf7c5379..32666729b 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -42,6 +42,7 @@ #### Various - [Security](security.md) + - [Azure](azure.md) - [SSL and TLS](ssl-and-tls.md) - [Pseudo GTID](pseudo-gtid.md): refactoring and high availability without using GTID. - [Agents](agents.md) diff --git a/go.mod b/go.mod index e96432ec9..479258d0f 100644 --- a/go.mod +++ b/go.mod @@ -1,48 +1,92 @@ module github.com/openark/orchestrator -go 1.16 +go 1.18 require ( + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 - github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect github.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432 - github.com/fatih/color v1.10.0 // indirect github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab github.com/go-sql-driver/mysql v1.6.0 - github.com/google/btree v1.0.0 // indirect + github.com/google/uuid v1.3.0 github.com/hashicorp/consul/api v1.7.0 + github.com/hashicorp/raft v0.0.0-00010101000000-000000000000 + github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c + github.com/martini-contrib/auth v0.0.0-20150219114609-fa62c19b7ae8 + github.com/martini-contrib/gzip v0.0.0-20151124214156-6c035326b43f + github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 + github.com/martini-contrib/sessions v0.0.0-20140630231722-fa13114fbcf0 + github.com/mattn/go-sqlite3 v1.14.7 + github.com/microsoftgraph/msgraph-sdk-go v0.55.0 + github.com/montanaflynn/stats v0.6.6 + github.com/openark/golib v0.0.0-20210520103621-827f3ea62180 + github.com/outbrain/zookeepercli v1.0.12 + github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 + github.com/samuel/go-zookeeper v0.0.0-20201211165307-7117e9ea2414 + github.com/sjmudd/stopwatch v0.0.0-20170103085848-637ef30077b7 + gopkg.in/gcfg.v1 v1.2.3 +) + +require ( + github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect + github.com/gomodule/redigo v2.0.0+incompatible // indirect + github.com/gorilla/context v1.1.1 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect + github.com/gorilla/sessions v1.2.1 // indirect +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1 // indirect + github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect + github.com/cjlapao/common-go v0.0.37 // indirect + github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang-jwt/jwt/v4 v4.4.2 // indirect + github.com/google/btree v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2-0.20190406162018-d3fcbee8e181 // indirect github.com/hashicorp/go-hclog v0.15.1-0.20201116205511-59fbd7b93270 // indirect github.com/hashicorp/go-immutable-radix v1.3.0 // indirect + github.com/hashicorp/go-msgpack v0.5.3 // indirect github.com/hashicorp/go-rootcerts v1.0.3-0.20191216101743-c8a9a31cbd76 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/hashicorp/raft v0.0.0-00010101000000-000000000000 github.com/hashicorp/serf v0.9.5 // indirect - github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c - github.com/martini-contrib/auth v0.0.0-20150219114609-fa62c19b7ae8 - github.com/martini-contrib/gzip v0.0.0-20151124214156-6c035326b43f - github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 - github.com/mattn/go-isatty v0.0.13-0.20200128103942-cb30d6282491 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/microsoft/kiota-abstractions-go v0.17.0 // indirect + github.com/microsoft/kiota-authentication-azure-go v0.6.0 // indirect + github.com/microsoft/kiota-http-go v0.14.0 // indirect + github.com/microsoft/kiota-serialization-form-go v0.3.0 // indirect + github.com/microsoft/kiota-serialization-json-go v0.8.1 // indirect + github.com/microsoft/kiota-serialization-text-go v0.7.0 // indirect + github.com/microsoftgraph/msgraph-sdk-go-core v0.33.1 // indirect github.com/miekg/dns v1.1.31 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.0 // indirect github.com/mitchellh/mapstructure v1.3.3 // indirect - github.com/montanaflynn/stats v0.0.0-20161102194025-f8cd06f93c6c - github.com/openark/golib v0.0.0-20210520103621-827f3ea62180 github.com/outbrain/golib v0.0.0-20200503083229-2531e5dbcc71 // indirect - github.com/outbrain/zookeepercli v1.0.12 github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect - github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 - github.com/samuel/go-zookeeper v0.0.0-20201211165307-7117e9ea2414 - github.com/sjmudd/stopwatch v0.0.0-20170103085848-637ef30077b7 - github.com/stretchr/testify v1.6.1 // indirect - golang.org/x/crypto v0.0.0-20200930160638-afb6bcd081ae // indirect - golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect - golang.org/x/sys v0.0.0-20201126233918-771906719818 // indirect - gopkg.in/gcfg.v1 v1.2.3 + github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.8.1 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect + go.opentelemetry.io/otel v1.11.2 // indirect + go.opentelemetry.io/otel/trace v1.11.2 // indirect + golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 // indirect + golang.org/x/net v0.5.0 // indirect + golang.org/x/sys v0.4.0 // indirect + golang.org/x/term v0.4.0 // indirect + golang.org/x/text v0.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/hashicorp/raft => github.com/openark/raft v0.0.0-20170918052300-fba9f909f7fe diff --git a/go.sum b/go.sum index 21d741544..b6cc6bb69 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,11 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.1 h1:gVXuXcWd1i4C2Ruxe321aU+IKGaStvGB/S90PUPB/W8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.1/go.mod h1:DffdKW9RFqa5VgmsjUOsS7UE7eiA5iAvYUs63bhKQ0M= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1 h1:T8quHYlUGyb/oqtSTwqlCr1ilJHrDv+ZtpSfo+hm1BU= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1/go.mod h1:gLa1CL2RNE4s7M3yopJ/p0iq5DdY6Yv5ZUt9MTRZOQM= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 h1:+5VZ72z0Qan5Bog5C+ZkgSqUbeVUd9wgtHOrIKuc5b8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1 h1:oPdPEZFSbl7oSPEAIPMPBMUmiL+mqgzBJwM/9qYcwNg= +github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1/go.mod h1:4qFor3D/HDsvBME35Xy9rwW9DecL+M2sNw1ybjPtwA0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -6,6 +14,10 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04= +github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= +github.com/cjlapao/common-go v0.0.37 h1:ITL+pNUKKbajV9/seV/qoNPCGDhBSp8BCpDggesgM/U= +github.com/cjlapao/common-go v0.0.37/go.mod h1:M3dzazLjTjEtZJbbxoA5ZDiGCiHmpwqW9l4UWaddwOA= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432 h1:M5QgkYacWj0Xs8MhpIK/5uwU02icXpEoSo9sM2aRCps= @@ -13,17 +25,37 @@ github.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432/go. 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/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +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 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/hashicorp/consul/api v1.7.0 h1:tGs8Oep67r8CcA2Ycmb/8BLBcJ70St44mF2X10a/qPg= github.com/hashicorp/consul/api v1.7.0/go.mod h1:1NSuaUUkFaJzMasbfq/11wKYWSR67Xn6r2DXKhuDNFg= github.com/hashicorp/consul/sdk v0.6.0 h1:FfhMEkwvQl57CildXJyGHnwGGM4HMODGyfjGwNM1Vdw= @@ -70,28 +102,50 @@ github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c/go.mod h1:lADxMC39cJ github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/martini-contrib/auth v0.0.0-20150219114609-fa62c19b7ae8 h1:1ded5x5QpCLsyTH5ct62Rh1RXPFnn0/dubCqAeh+stU= github.com/martini-contrib/auth v0.0.0-20150219114609-fa62c19b7ae8/go.mod h1:ahTFgV/NtzY/CALneRrC67m1dis5arHTQDfyIhKk69E= github.com/martini-contrib/gzip v0.0.0-20151124214156-6c035326b43f h1:wVDxEVZP1eiPIlHVaafUAEUDtyl6ytjHv3egJVbyfOk= github.com/martini-contrib/gzip v0.0.0-20151124214156-6c035326b43f/go.mod h1:jhUB0rZB2TPWqy0yGugKRRictO591eSO7If7O4MfCaA= github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw= github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI= +github.com/martini-contrib/sessions v0.0.0-20140630231722-fa13114fbcf0 h1:kRwknBBQVnRz0BF2wzTZARcaSsYtNdOGL5iQsqPtWUc= +github.com/martini-contrib/sessions v0.0.0-20140630231722-fa13114fbcf0/go.mod h1:adYdHCQ10x7n+4GhRwUv+x6uRLIKkDfBuVORfFduvwQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.13-0.20200128103942-cb30d6282491 h1:jygFUAtCyqjM5JGFNAWcXLqlXArQqxDZ3DqQer1BIik= -github.com/mattn/go-isatty v0.0.13-0.20200128103942-cb30d6282491/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA= github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/microsoft/kiota-abstractions-go v0.17.0 h1:Ye2DTk8ko9Na0uCvhcCV7TQPWt72trT+kyD37btDtsI= +github.com/microsoft/kiota-abstractions-go v0.17.0/go.mod h1:RT/s9sCzg49i4iO7e2qhyWmX+DlJDgC0P+Wp8fKQQfo= +github.com/microsoft/kiota-authentication-azure-go v0.6.0 h1:Il9bLO34J6D8DY89xYAXoGh9muvlphayqG4eihyT6B8= +github.com/microsoft/kiota-authentication-azure-go v0.6.0/go.mod h1:EJCHiLWLXW1/mSgX7lYReAhVO37MzRT5Xi2mcPTwCRQ= +github.com/microsoft/kiota-http-go v0.14.0 h1:3Bzm8gKUIfOOVj3f+O0HLxwdrVLpjObrOBd7UqcKNsY= +github.com/microsoft/kiota-http-go v0.14.0/go.mod h1:oGtssZnOKhM5EYNHxLyfX7r0C3uDIi1WUZuwmpb6RkA= +github.com/microsoft/kiota-serialization-form-go v0.3.0 h1:aWSIVkR86gFy5DXHsqoY+Z6NnNwELfR2pSU6JjpwDo0= +github.com/microsoft/kiota-serialization-form-go v0.3.0/go.mod h1:kvvyXmxOpkJ0Llwx7w9W3vgeJh5RX83v5NKm2F4pajs= +github.com/microsoft/kiota-serialization-json-go v0.8.1 h1:2mRdYwpx7ZLGn0vrBK4/btr5YHcJzeauU+xfpIE0+08= +github.com/microsoft/kiota-serialization-json-go v0.8.1/go.mod h1:SerZlR2WDr+jygmkeZpo9U2TGX8Q84OfHrqLkQPkFZ0= +github.com/microsoft/kiota-serialization-text-go v0.7.0 h1:uayeq8fpDcZgL0zDyLkYZsH6zNnEXKgp+bRWfR5LcxA= +github.com/microsoft/kiota-serialization-text-go v0.7.0/go.mod h1:2su1PTllHCMNkHugmvpYad+AKBXUUGoiNP3xOAJUL7w= +github.com/microsoftgraph/msgraph-sdk-go v0.55.0 h1:iIhLZ00gNh3oGyOP7GuHnjlbvB+lwn94K6Alj8qBAsM= +github.com/microsoftgraph/msgraph-sdk-go v0.55.0/go.mod h1:t8j6z4Rn9OdwgmjAMh8vwXRcEZNpTBTzQEb9IvZI3ZE= +github.com/microsoftgraph/msgraph-sdk-go-core v0.33.1 h1:BO6TdlnOQOHK5HKr3oYFmKGcs2uxgsAg9NGXF6k7Xf4= +github.com/microsoftgraph/msgraph-sdk-go-core v0.33.1/go.mod h1:VFtdk6iAvn9M8IW8fbRXcEfyzSvfH1CZgzV5DtJmlXE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= @@ -108,8 +162,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/montanaflynn/stats v0.0.0-20161102194025-f8cd06f93c6c h1:oejBmbfK9+vFwZT8ORTkAJ1H0E6L1Kvh7SHrNq7lsJ8= -github.com/montanaflynn/stats v0.0.0-20161102194025-f8cd06f93c6c/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ= +github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/openark/golib v0.0.0-20210520103621-827f3ea62180 h1:qUVSr51MtB1GYOCYh7vuOMk4N6fM9kDyD4MJqfhfmzQ= github.com/openark/golib v0.0.0-20210520103621-827f3ea62180/go.mod h1:tI1Dzurj0PcCcB7cAr+uVHqTFwP/K26V68smpLLvmvE= github.com/openark/raft v0.0.0-20170918052300-fba9f909f7fe h1:nDBVQWvNTux+axxbi4PHiJCUZ4MkM5FQ44VQW/NbRBE= @@ -124,8 +178,10 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -141,27 +197,35 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/sjmudd/stopwatch v0.0.0-20170103085848-637ef30077b7 h1:kYdcxORIMCkzStUfR8FnynBNUEhx3hnoQN2dHvfcd2Y= github.com/sjmudd/stopwatch v0.0.0-20170103085848-637ef30077b7/go.mod h1:Pgf1sZ2KrHK8vdRTV5UHGp80LT7HMUKuNAiKC402abY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0= +go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI= +go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZM/+y0= +go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200930160638-afb6bcd081ae h1:duLSQW+DZ5MsXKX7kc4rXlq6/mmxz4G6ewJuBPlhRe0= -golang.org/x/crypto v0.0.0-20200930160638-afb6bcd081ae/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 h1:Tgea0cVUD0ivh5ADBX4WwuI12DUd2to3nCYe2eayMIw= +golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo= -golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -175,11 +239,16 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201126233918-771906719818 h1:f1CIuDlJhwANEC2MM87MBEVMr3jl5bifgsfj90XAF9c= -golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -194,5 +263,7 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go/app/http.go b/go/app/http.go index d63f57e8b..8ae8642df 100644 --- a/go/app/http.go +++ b/go/app/http.go @@ -35,6 +35,7 @@ import ( "github.com/martini-contrib/auth" "github.com/martini-contrib/gzip" "github.com/martini-contrib/render" + "github.com/martini-contrib/sessions" "github.com/openark/golib/log" ) @@ -99,6 +100,33 @@ func standardHttp(continuousDiscovery bool) { return auth.SecureCompare(username, config.Config.HTTPAuthUser) && auth.SecureCompare(password, config.Config.HTTPAuthPassword) })) } + case "azure": + { + if config.Config.AzureClientID == "" { + log.Fatal("AuthenticationMethod is configured as 'azure' but AzureClientID undefined") + } + if config.Config.AzureGraphUserScope == "" { + log.Fatal("AuthenticationMethod is configured as 'azure' but AzureGraphUserScope undefined") + } + if config.Config.AzureRedirectURL == "" { + log.Fatal("AuthenticationMethod is configured as 'azure' but AzureRedirectURL undefined") + } + if config.Config.AzureTenantID == "" { + log.Fatal("AuthenticationMethod is configured as 'azure' but AzureTenantID undefined") + } + if config.Config.AzureApplicationName == "" { + log.Fatal("AuthenticationMethod is configured as 'azure' but AzureApplicationName undefined") + } + if config.Config.AzureApplicationID == "" { + log.Fatal("AuthenticationMethod is configured as 'azure' but AzureApplicationID undefined") + } + if config.Config.AzureAdminRole == "" { + log.Warning("AuthenticationMethod is configured as 'azure' but AzureAdminRole undefined. Default value will be set to 'admin'") + } + + // We inject a dummy User object because we have function signatures with User argument in api.go + m.Map(auth.User("")) + } default: { // We inject a dummy User object because we have function signatures with User argument in api.go @@ -106,6 +134,9 @@ func standardHttp(continuousDiscovery bool) { } } + store := sessions.NewCookieStore([]byte(config.Config.SessionSecretKey)) + m.Use(sessions.Sessions("go_session", store)) + m.Use(gzip.All()) // Render html templates from templates directory m.Use(render.Renderer(render.Options{ diff --git a/go/azure/graph.go b/go/azure/graph.go new file mode 100644 index 000000000..16d8cae6e --- /dev/null +++ b/go/azure/graph.go @@ -0,0 +1,116 @@ +package azure + +import ( + "context" + "log" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/google/uuid" + msgraphsdkgo "github.com/microsoftgraph/msgraph-sdk-go" + "github.com/microsoftgraph/msgraph-sdk-go/me" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/openark/orchestrator/go/config" +) + +type GraphHelper struct { + usernamePasswordCredential *azidentity.UsernamePasswordCredential + graphUserScopes []string + appClient *msgraphsdkgo.GraphServiceClient +} + +func NewGraphHelper() GraphHelper { + g := GraphHelper{} + return g +} + +func (g *GraphHelper) InitializeGraphForAppAuth(email string, password string) error { + clientId := config.Config.AzureClientID + tenantId := config.Config.AzureTenantID + scope := config.Config.AzureGraphUserScope + g.graphUserScopes = strings.Split(scope, ",") + cred, err := azidentity.NewUsernamePasswordCredential(tenantId, clientId, email, password, nil) + if err != nil { + log.Fatal("New Browser Cred Error: ", err) + } + g.usernamePasswordCredential = cred + + client, err := msgraphsdkgo.NewGraphServiceClientWithCredentials(cred, g.graphUserScopes) + g.appClient = client + + return err + +} + +func (g *GraphHelper) GetUser() (models.Userable, error) { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + query := me.MeRequestBuilderGetQueryParameters{ + // Only request specific properties + Select: []string{"displayName", "id"}, + } + user, err := g.appClient.Me().Get(ctx, &me.MeRequestBuilderGetRequestConfiguration{ + QueryParameters: &query, + }) + return user, err +} + +func (g *GraphHelper) GetAppRoleAssignments(userId string, appName string, appID string) (string, error) { + var hasUserRole bool + var hasGroupRole bool + var userAppRoleId uuid.UUID + var groupAppRoleId uuid.UUID + var roleToCheck uuid.UUID + var roleString string + + ctxAppRoleAssignements, cancelAppRoleAssignements := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelAppRoleAssignements() + + // Get User RoleID for appName + appRoleAssignements, err := g.appClient.UsersById(userId).AppRoleAssignments().Get(ctxAppRoleAssignements, nil) + + values := appRoleAssignements.GetValue() + for _, value := range values { + if *value.GetResourceDisplayName() == appName { + if *value.GetPrincipalType() == "User" { + hasUserRole = true + userAppRoleId = *value.GetAppRoleId() + } else { + hasGroupRole = true + groupAppRoleId = *value.GetAppRoleId() + } + } + } + + if hasUserRole { + roleToCheck = userAppRoleId + } else if !hasUserRole && hasGroupRole { + roleToCheck = groupAppRoleId + } else { + roleToCheck = userAppRoleId + } + + ctxAppRoles, cancelAppRoles := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelAppRoles() + + // Get Application Roles + appRoles, err := g.appClient.ApplicationsById(appID).Get(ctxAppRoles, nil) + + roles := appRoles.GetAppRoles() + for _, role := range roles { + if *role.GetId() == roleToCheck { + roleString = *role.GetValue() + break + } + } + + return roleString, err +} + +func (g *GraphHelper) Clear() { + g.usernamePasswordCredential = nil + g.graphUserScopes = nil + g.appClient = nil +} diff --git a/go/azure/session.go b/go/azure/session.go new file mode 100644 index 000000000..4e3e08650 --- /dev/null +++ b/go/azure/session.go @@ -0,0 +1,34 @@ +package azure + +import ( + "github.com/martini-contrib/sessions" +) + +func GetUsername(session sessions.Session) string { + username := session.Get("Username") + if username == nil { + return "" + } + + return username.(string) +} + +func GetUserRole(session sessions.Session) string { + role := session.Get("Role") + if role == nil { + return "" + } + + return role.(string) +} + +func IsUserLogged(session sessions.Session) bool { + username := GetUsername(session) + role := GetUserRole(session) + + if username != "" && role != "" { + return true + } + + return false +} diff --git a/go/azure/user.go b/go/azure/user.go new file mode 100644 index 000000000..efe4bfead --- /dev/null +++ b/go/azure/user.go @@ -0,0 +1,27 @@ +package azure + +import ( + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/openark/golib/log" + "github.com/openark/orchestrator/go/config" +) + +func GetAzureUser(g *GraphHelper) models.Userable { + guser, err := g.GetUser() + + if err != nil { + log.Error("Error getting user: ", err) + return nil + } + + return guser +} + +func GetAzureUserAppRoleAssignments(guser models.Userable, g *GraphHelper) string { + userRole, err := g.GetAppRoleAssignments(*guser.GetId(), config.Config.AzureApplicationName, config.Config.AzureApplicationID) + if err != nil { + log.Error("Error getting user information: ", err) + } + + return userRole +} diff --git a/go/config/config.go b/go/config/config.go index 5d65da4b9..9ae20019b 100644 --- a/go/config/config.go +++ b/go/config/config.go @@ -175,7 +175,15 @@ type Configuration struct { OAuthScopes []string HTTPAuthUser string // Username for HTTP Basic authentication (blank disables authentication) HTTPAuthPassword string // Password for HTTP Basic authentication + SessionSecretKey string // Random secret key used for session AuthUserHeader string // HTTP header indicating auth user, when AuthenticationMethod is "proxy" + AzureClientID string // Application Client ID used by Azure + AzureTenantID string // The AzureTenantID setting will vary depending on what “Supported Account Type” is chosen. It will be either “common”, “organizations”, “consumers” or a tenant ID. + AzureGraphUserScope string // Scope for Microsoft Graph. 'User.Read' will be enought + AzureRedirectURL string // Redirect URL specified in the Azure Application + AzureApplicationName string // Azure Application Name + AzureApplicationID string // Azure Application ID + AzureAdminRole string // Value of admin 'role' in Azure PowerAuthUsers []string // On AuthenticationMethod == "proxy", list of users that can make changes. All others are read-only. PowerAuthGroups []string // list of unix groups the authenticated user must be a member of to make changes. AccessTokenUseExpirySeconds uint // Time by which an issued token must be used @@ -359,6 +367,14 @@ func newConfiguration() *Configuration { HTTPAuthUser: "", HTTPAuthPassword: "", AuthUserHeader: "X-Forwarded-User", + SessionSecretKey: "secret", + AzureClientID: "", + AzureTenantID: "", + AzureGraphUserScope: "", + AzureRedirectURL: "", + AzureApplicationName: "", + AzureApplicationID: "", + AzureAdminRole: "", PowerAuthUsers: []string{"*"}, PowerAuthGroups: []string{}, AccessTokenUseExpirySeconds: 60, diff --git a/go/http/api.go b/go/http/api.go index d4c43d0e0..1c236e427 100644 --- a/go/http/api.go +++ b/go/http/api.go @@ -28,6 +28,7 @@ import ( "github.com/go-martini/martini" "github.com/martini-contrib/auth" "github.com/martini-contrib/render" + "github.com/martini-contrib/sessions" "github.com/openark/golib/log" "github.com/openark/golib/util" @@ -208,8 +209,8 @@ func (this *HttpAPI) Instance(params martini.Params, r render.Render, req *http. // AsyncDiscover issues an asynchronous read on an instance. This is // useful for bulk loads of a new set of instances and will not block // if the instance is slow to respond or not reachable. -func (this *HttpAPI) AsyncDiscover(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AsyncDiscover(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -218,14 +219,14 @@ func (this *HttpAPI) AsyncDiscover(params martini.Params, r render.Render, req * Respond(r, &APIResponse{Code: ERROR, Message: err.Error()}) return } - go this.Discover(params, r, req, user) + go this.Discover(params, r, req, user, s) Respond(r, &APIResponse{Code: OK, Message: fmt.Sprintf("Asynchronous discovery initiated for Instance: %+v", instanceKey)}) } // Discover issues a synchronous read on an instance -func (this *HttpAPI) Discover(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) Discover(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -250,8 +251,8 @@ func (this *HttpAPI) Discover(params martini.Params, r render.Render, req *http. } // Refresh synchronuously re-reads a topology instance -func (this *HttpAPI) Refresh(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) Refresh(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -272,8 +273,8 @@ func (this *HttpAPI) Refresh(params martini.Params, r render.Render, req *http.R } // Forget removes an instance entry fro backend database -func (this *HttpAPI) Forget(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) Forget(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -296,8 +297,8 @@ func (this *HttpAPI) Forget(params martini.Params, r render.Render, req *http.Re } // ForgetCluster forgets all instacnes of a cluster -func (this *HttpAPI) ForgetCluster(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) ForgetCluster(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -335,8 +336,8 @@ func (this *HttpAPI) Resolve(params martini.Params, r render.Render, req *http.R } // BeginMaintenance begins maintenance mode for given instance -func (this *HttpAPI) BeginMaintenance(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) BeginMaintenance(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -356,8 +357,8 @@ func (this *HttpAPI) BeginMaintenance(params martini.Params, r render.Render, re } // EndMaintenance terminates maintenance mode -func (this *HttpAPI) EndMaintenance(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) EndMaintenance(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -376,8 +377,8 @@ func (this *HttpAPI) EndMaintenance(params martini.Params, r render.Render, req } // EndMaintenanceByInstanceKey terminates maintenance mode for given instance -func (this *HttpAPI) EndMaintenanceByInstanceKey(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) EndMaintenanceByInstanceKey(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -428,8 +429,8 @@ func (this *HttpAPI) Maintenance(params martini.Params, r render.Render, req *ht } // BeginDowntime sets a downtime flag with default duration -func (this *HttpAPI) BeginDowntime(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) BeginDowntime(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -468,8 +469,8 @@ func (this *HttpAPI) BeginDowntime(params martini.Params, r render.Render, req * } // EndDowntime terminates downtime (removes downtime flag) for an instance -func (this *HttpAPI) EndDowntime(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) EndDowntime(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -493,8 +494,8 @@ func (this *HttpAPI) EndDowntime(params martini.Params, r render.Render, req *ht } // MoveUp attempts to move an instance up the topology -func (this *HttpAPI) MoveUp(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MoveUp(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -514,8 +515,8 @@ func (this *HttpAPI) MoveUp(params martini.Params, r render.Render, req *http.Re } // MoveUpReplicas attempts to move up all replicas of an instance -func (this *HttpAPI) MoveUpReplicas(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MoveUpReplicas(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -536,8 +537,8 @@ func (this *HttpAPI) MoveUpReplicas(params martini.Params, r render.Render, req // Repoint positiones a replica under another (or same) master with exact same coordinates. // Useful for binlog servers -func (this *HttpAPI) Repoint(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) Repoint(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -562,8 +563,8 @@ func (this *HttpAPI) Repoint(params martini.Params, r render.Render, req *http.R } // MoveUpReplicas attempts to move up all replicas of an instance -func (this *HttpAPI) RepointReplicas(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RepointReplicas(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -583,8 +584,8 @@ func (this *HttpAPI) RepointReplicas(params martini.Params, r render.Render, req } // MakeCoMaster attempts to make an instance co-master with its own master -func (this *HttpAPI) MakeCoMaster(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MakeCoMaster(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -604,8 +605,8 @@ func (this *HttpAPI) MakeCoMaster(params martini.Params, r render.Render, req *h } // ResetReplication makes a replica forget about its master, effectively breaking the replication -func (this *HttpAPI) ResetReplication(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) ResetReplication(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -626,8 +627,8 @@ func (this *HttpAPI) ResetReplication(params martini.Params, r render.Render, re // DetachReplicaMasterHost detaches a replica from its master by setting an invalid // (yet revertible) host name -func (this *HttpAPI) DetachReplicaMasterHost(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) DetachReplicaMasterHost(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -648,8 +649,8 @@ func (this *HttpAPI) DetachReplicaMasterHost(params martini.Params, r render.Ren // ReattachReplicaMasterHost reverts a detachReplicaMasterHost command // by resoting the original master hostname in CHANGE MASTER TO -func (this *HttpAPI) ReattachReplicaMasterHost(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) ReattachReplicaMasterHost(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -669,8 +670,8 @@ func (this *HttpAPI) ReattachReplicaMasterHost(params martini.Params, r render.R } // EnableGTID attempts to enable GTID on a replica -func (this *HttpAPI) EnableGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) EnableGTID(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -690,8 +691,8 @@ func (this *HttpAPI) EnableGTID(params martini.Params, r render.Render, req *htt } // DisableGTID attempts to disable GTID on a replica, and revert to binlog file:pos -func (this *HttpAPI) DisableGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) DisableGTID(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -727,8 +728,8 @@ func (this *HttpAPI) LocateErrantGTID(params martini.Params, r render.Render, re } // ErrantGTIDResetMaster removes errant transactions on a server by way of RESET MASTER -func (this *HttpAPI) ErrantGTIDResetMaster(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) ErrantGTIDResetMaster(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -748,8 +749,8 @@ func (this *HttpAPI) ErrantGTIDResetMaster(params martini.Params, r render.Rende } // ErrantGTIDInjectEmpty removes errant transactions by injecting and empty transaction on the cluster's master -func (this *HttpAPI) ErrantGTIDInjectEmpty(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) ErrantGTIDInjectEmpty(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -769,8 +770,8 @@ func (this *HttpAPI) ErrantGTIDInjectEmpty(params martini.Params, r render.Rende } // MoveBelow attempts to move an instance below its supposed sibling -func (this *HttpAPI) MoveBelow(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MoveBelow(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -795,8 +796,8 @@ func (this *HttpAPI) MoveBelow(params martini.Params, r render.Render, req *http } // MoveBelowGTID attempts to move an instance below another, via GTID -func (this *HttpAPI) MoveBelowGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MoveBelowGTID(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -821,8 +822,8 @@ func (this *HttpAPI) MoveBelowGTID(params martini.Params, r render.Render, req * } // MoveReplicasGTID attempts to move an instance below another, via GTID -func (this *HttpAPI) MoveReplicasGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MoveReplicasGTID(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -847,8 +848,8 @@ func (this *HttpAPI) MoveReplicasGTID(params martini.Params, r render.Render, re } // TakeSiblings -func (this *HttpAPI) TakeSiblings(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) TakeSiblings(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -868,8 +869,8 @@ func (this *HttpAPI) TakeSiblings(params martini.Params, r render.Render, req *h } // TakeMaster -func (this *HttpAPI) TakeMaster(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) TakeMaster(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -890,8 +891,8 @@ func (this *HttpAPI) TakeMaster(params martini.Params, r render.Render, req *htt // RelocateBelow attempts to move an instance below another, orchestrator choosing the best (potentially multi-step) // relocation method -func (this *HttpAPI) RelocateBelow(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RelocateBelow(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -916,8 +917,8 @@ func (this *HttpAPI) RelocateBelow(params martini.Params, r render.Render, req * } // Relocates attempts to smartly relocate replicas of a given instance below another -func (this *HttpAPI) RelocateReplicas(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RelocateReplicas(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -942,8 +943,8 @@ func (this *HttpAPI) RelocateReplicas(params martini.Params, r render.Render, re } // MoveEquivalent attempts to move an instance below another, baseed on known equivalence master coordinates -func (this *HttpAPI) MoveEquivalent(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MoveEquivalent(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -968,8 +969,8 @@ func (this *HttpAPI) MoveEquivalent(params martini.Params, r render.Render, req } // LastPseudoGTID attempts to find the last pseugo-gtid entry in an instance -func (this *HttpAPI) LastPseudoGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) LastPseudoGTID(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1003,8 +1004,8 @@ func (this *HttpAPI) LastPseudoGTID(params martini.Params, r render.Render, req } // MatchBelow attempts to move an instance below another via pseudo GTID matching of binlog entries -func (this *HttpAPI) MatchBelow(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MatchBelow(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1029,8 +1030,8 @@ func (this *HttpAPI) MatchBelow(params martini.Params, r render.Render, req *htt } // MatchBelow attempts to move an instance below another via pseudo GTID matching of binlog entries -func (this *HttpAPI) MatchUp(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MatchUp(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1050,8 +1051,8 @@ func (this *HttpAPI) MatchUp(params martini.Params, r render.Render, req *http.R } // MultiMatchReplicas attempts to match all replicas of a given instance below another, efficiently -func (this *HttpAPI) MultiMatchReplicas(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MultiMatchReplicas(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1076,8 +1077,8 @@ func (this *HttpAPI) MultiMatchReplicas(params martini.Params, r render.Render, } // MatchUpReplicas attempts to match up all replicas of an instance -func (this *HttpAPI) MatchUpReplicas(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MatchUpReplicas(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1098,8 +1099,8 @@ func (this *HttpAPI) MatchUpReplicas(params martini.Params, r render.Render, req // RegroupReplicas attempts to pick a replica of a given instance and make it take its siblings, using any // method possible (GTID, Pseudo-GTID, binlog servers) -func (this *HttpAPI) RegroupReplicas(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RegroupReplicas(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1122,8 +1123,8 @@ func (this *HttpAPI) RegroupReplicas(params martini.Params, r render.Render, req // RegroupReplicas attempts to pick a replica of a given instance and make it take its siblings, efficiently, // using pseudo-gtid if necessary -func (this *HttpAPI) RegroupReplicasPseudoGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RegroupReplicasPseudoGTID(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1146,8 +1147,8 @@ func (this *HttpAPI) RegroupReplicasPseudoGTID(params martini.Params, r render.R } // RegroupReplicasGTID attempts to pick a replica of a given instance and make it take its siblings, efficiently, using GTID -func (this *HttpAPI) RegroupReplicasGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RegroupReplicasGTID(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1170,8 +1171,8 @@ func (this *HttpAPI) RegroupReplicasGTID(params martini.Params, r render.Render, } // RegroupReplicasBinlogServers attempts to pick a replica of a given instance and make it take its siblings, efficiently, using GTID -func (this *HttpAPI) RegroupReplicasBinlogServers(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RegroupReplicasBinlogServers(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1193,8 +1194,8 @@ func (this *HttpAPI) RegroupReplicasBinlogServers(params martini.Params, r rende } // MakeMaster attempts to make the given instance a master, and match its siblings to be its replicas -func (this *HttpAPI) MakeMaster(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MakeMaster(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1215,8 +1216,8 @@ func (this *HttpAPI) MakeMaster(params martini.Params, r render.Render, req *htt // MakeLocalMaster attempts to make the given instance a local master: take over its master by // enslaving its siblings and replicating from its grandparent. -func (this *HttpAPI) MakeLocalMaster(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MakeLocalMaster(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1236,8 +1237,8 @@ func (this *HttpAPI) MakeLocalMaster(params martini.Params, r render.Render, req } // SkipQuery skips a single query on a failed replication instance -func (this *HttpAPI) SkipQuery(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) SkipQuery(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1257,8 +1258,8 @@ func (this *HttpAPI) SkipQuery(params martini.Params, r render.Render, req *http } // StartReplication starts replication on given instance -func (this *HttpAPI) StartReplication(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) StartReplication(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1278,8 +1279,8 @@ func (this *HttpAPI) StartReplication(params martini.Params, r render.Render, re } // RestartReplication stops & starts replication on given instance -func (this *HttpAPI) RestartReplication(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RestartReplication(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1299,8 +1300,8 @@ func (this *HttpAPI) RestartReplication(params martini.Params, r render.Render, } // StopReplication stops replication on given instance -func (this *HttpAPI) StopReplication(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) StopReplication(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1320,8 +1321,8 @@ func (this *HttpAPI) StopReplication(params martini.Params, r render.Render, req } // StopReplicationNicely stops replication on given instance, such that sql thead is aligned with IO thread -func (this *HttpAPI) StopReplicationNicely(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) StopReplicationNicely(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1341,8 +1342,8 @@ func (this *HttpAPI) StopReplicationNicely(params martini.Params, r render.Rende } // FlushBinaryLogs runs a single FLUSH BINARY LOGS -func (this *HttpAPI) FlushBinaryLogs(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) FlushBinaryLogs(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1362,8 +1363,8 @@ func (this *HttpAPI) FlushBinaryLogs(params martini.Params, r render.Render, req } // PurgeBinaryLogs purges binary logs up to given binlog file -func (this *HttpAPI) PurgeBinaryLogs(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) PurgeBinaryLogs(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1396,8 +1397,8 @@ func (this *HttpAPI) PurgeBinaryLogs(params martini.Params, r render.Render, req // RestartReplicationStatements receives a query to execute that requires a replication restart to apply. // As an example, this may be `set global rpl_semi_sync_slave_enabled=1`. orchestrator will check // replication status on given host and will wrap with appropriate stop/start statements, if need be. -func (this *HttpAPI) RestartReplicationStatements(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RestartReplicationStatements(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1420,8 +1421,8 @@ func (this *HttpAPI) RestartReplicationStatements(params martini.Params, r rende } // MasterEquivalent provides (possibly empty) list of master coordinates equivalent to the given ones -func (this *HttpAPI) MasterEquivalent(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) MasterEquivalent(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1521,8 +1522,8 @@ func (this *HttpAPI) CanReplicateFromGTID(params martini.Params, r render.Render } // setSemiSyncMaster -func (this *HttpAPI) setSemiSyncMaster(params martini.Params, r render.Render, req *http.Request, user auth.User, enable bool) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) setSemiSyncMaster(params martini.Params, r render.Render, req *http.Request, user auth.User, enable bool, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1541,16 +1542,16 @@ func (this *HttpAPI) setSemiSyncMaster(params martini.Params, r render.Render, r Respond(r, &APIResponse{Code: OK, Message: fmt.Sprintf("master semi-sync set to %t", enable), Details: instance}) } -func (this *HttpAPI) EnableSemiSyncMaster(params martini.Params, r render.Render, req *http.Request, user auth.User) { - this.setSemiSyncMaster(params, r, req, user, true) +func (this *HttpAPI) EnableSemiSyncMaster(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.setSemiSyncMaster(params, r, req, user, true, s) } -func (this *HttpAPI) DisableSemiSyncMaster(params martini.Params, r render.Render, req *http.Request, user auth.User) { - this.setSemiSyncMaster(params, r, req, user, false) +func (this *HttpAPI) DisableSemiSyncMaster(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.setSemiSyncMaster(params, r, req, user, false, s) } // setSemiSyncMaster -func (this *HttpAPI) setSemiSyncReplica(params martini.Params, r render.Render, req *http.Request, user auth.User, enable bool) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) setSemiSyncReplica(params martini.Params, r render.Render, req *http.Request, user auth.User, enable bool, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1569,17 +1570,17 @@ func (this *HttpAPI) setSemiSyncReplica(params martini.Params, r render.Render, Respond(r, &APIResponse{Code: OK, Message: fmt.Sprintf("replica semi-sync set to %t", enable), Details: instance}) } -func (this *HttpAPI) EnableSemiSyncReplica(params martini.Params, r render.Render, req *http.Request, user auth.User) { - this.setSemiSyncReplica(params, r, req, user, true) +func (this *HttpAPI) EnableSemiSyncReplica(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.setSemiSyncReplica(params, r, req, user, true, s) } -func (this *HttpAPI) DisableSemiSyncReplica(params martini.Params, r render.Render, req *http.Request, user auth.User) { - this.setSemiSyncReplica(params, r, req, user, false) +func (this *HttpAPI) DisableSemiSyncReplica(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.setSemiSyncReplica(params, r, req, user, false, s) } // DelayReplication delays replication on given instance with given seconds -func (this *HttpAPI) DelayReplication(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) DelayReplication(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1603,8 +1604,8 @@ func (this *HttpAPI) DelayReplication(params martini.Params, r render.Render, re } // SetReadOnly sets the global read_only variable -func (this *HttpAPI) SetReadOnly(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) SetReadOnly(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1624,8 +1625,8 @@ func (this *HttpAPI) SetReadOnly(params martini.Params, r render.Render, req *ht } // SetWriteable clear the global read_only variable -func (this *HttpAPI) SetWriteable(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) SetWriteable(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1645,8 +1646,8 @@ func (this *HttpAPI) SetWriteable(params martini.Params, r render.Render, req *h } // KillQuery kills a query running on a server -func (this *HttpAPI) KillQuery(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) KillQuery(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -1807,8 +1808,8 @@ func (this *HttpAPI) ClusterOSCReplicas(params martini.Params, r render.Render, } // SetClusterAlias will change an alias for a given clustername -func (this *HttpAPI) SetClusterAliasManualOverride(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) SetClusterAliasManualOverride(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2115,8 +2116,8 @@ func (this *HttpAPI) HostnameResolveCache(params martini.Params, r render.Render } // ResetHostnameResolveCache clears in-memory hostname resovle cache -func (this *HttpAPI) ResetHostnameResolveCache(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) ResetHostnameResolveCache(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2131,8 +2132,8 @@ func (this *HttpAPI) ResetHostnameResolveCache(params martini.Params, r render.R } // DeregisterHostnameUnresolve deregisters the unresolve name used previously -func (this *HttpAPI) DeregisterHostnameUnresolve(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) DeregisterHostnameUnresolve(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2157,8 +2158,8 @@ func (this *HttpAPI) DeregisterHostnameUnresolve(params martini.Params, r render } // RegisterHostnameUnresolve registers the unresolve name to use -func (this *HttpAPI) RegisterHostnameUnresolve(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RegisterHostnameUnresolve(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2184,8 +2185,8 @@ func (this *HttpAPI) RegisterHostnameUnresolve(params martini.Params, r render.R } // SubmitPoolInstances (re-)applies the list of hostnames for a given pool -func (this *HttpAPI) SubmitPoolInstances(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) SubmitPoolInstances(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2208,8 +2209,8 @@ func (this *HttpAPI) SubmitPoolInstances(params martini.Params, r render.Render, } // SubmitPoolHostnames (re-)applies the list of hostnames for a given pool -func (this *HttpAPI) ReadClusterPoolInstancesMap(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) ReadClusterPoolInstancesMap(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2226,8 +2227,8 @@ func (this *HttpAPI) ReadClusterPoolInstancesMap(params martini.Params, r render } // GetHeuristicClusterPoolInstances returns instances belonging to a cluster's pool -func (this *HttpAPI) GetHeuristicClusterPoolInstances(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) GetHeuristicClusterPoolInstances(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2248,8 +2249,8 @@ func (this *HttpAPI) GetHeuristicClusterPoolInstances(params martini.Params, r r } // GetHeuristicClusterPoolInstances returns instances belonging to a cluster's pool -func (this *HttpAPI) GetHeuristicClusterPoolInstancesLag(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) GetHeuristicClusterPoolInstancesLag(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2270,8 +2271,8 @@ func (this *HttpAPI) GetHeuristicClusterPoolInstancesLag(params martini.Params, } // ReloadClusterAlias clears in-memory hostname resovle cache -func (this *HttpAPI) ReloadClusterAlias(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) ReloadClusterAlias(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2280,8 +2281,8 @@ func (this *HttpAPI) ReloadClusterAlias(params martini.Params, r render.Render, } // BulkPromotionRules returns a list of the known promotion rules for each instance -func (this *HttpAPI) BulkPromotionRules(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) BulkPromotionRules(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2296,8 +2297,8 @@ func (this *HttpAPI) BulkPromotionRules(params martini.Params, r render.Render, } // BulkInstances returns a list of all known instances -func (this *HttpAPI) BulkInstances(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) BulkInstances(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2454,8 +2455,8 @@ func (this *HttpAPI) WriteBufferMetricsAggregated(params martini.Params, r rende } // Agents provides complete list of registered agents (See https://github.com/openark/orchestrator-agent) -func (this *HttpAPI) Agents(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) Agents(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2475,8 +2476,8 @@ func (this *HttpAPI) Agents(params martini.Params, r render.Render, req *http.Re } // Agent returns complete information of a given agent -func (this *HttpAPI) Agent(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) Agent(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2496,8 +2497,8 @@ func (this *HttpAPI) Agent(params martini.Params, r render.Render, req *http.Req } // AgentUnmount instructs an agent to unmount the designated mount point -func (this *HttpAPI) AgentUnmount(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AgentUnmount(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2517,8 +2518,8 @@ func (this *HttpAPI) AgentUnmount(params martini.Params, r render.Render, req *h } // AgentMountLV instructs an agent to mount a given volume on the designated mount point -func (this *HttpAPI) AgentMountLV(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AgentMountLV(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2538,8 +2539,8 @@ func (this *HttpAPI) AgentMountLV(params martini.Params, r render.Render, req *h } // AgentCreateSnapshot instructs an agent to create a new snapshot. Agent's DIY implementation. -func (this *HttpAPI) AgentCreateSnapshot(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AgentCreateSnapshot(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2559,8 +2560,8 @@ func (this *HttpAPI) AgentCreateSnapshot(params martini.Params, r render.Render, } // AgentRemoveLV instructs an agent to remove a logical volume -func (this *HttpAPI) AgentRemoveLV(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AgentRemoveLV(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2580,8 +2581,8 @@ func (this *HttpAPI) AgentRemoveLV(params martini.Params, r render.Render, req * } // AgentMySQLStop stops MySQL service on agent -func (this *HttpAPI) AgentMySQLStop(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AgentMySQLStop(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2601,8 +2602,8 @@ func (this *HttpAPI) AgentMySQLStop(params martini.Params, r render.Render, req } // AgentMySQLStart starts MySQL service on agent -func (this *HttpAPI) AgentMySQLStart(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AgentMySQLStart(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2621,8 +2622,8 @@ func (this *HttpAPI) AgentMySQLStart(params martini.Params, r render.Render, req r.JSON(http.StatusOK, output) } -func (this *HttpAPI) AgentCustomCommand(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AgentCustomCommand(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2643,8 +2644,8 @@ func (this *HttpAPI) AgentCustomCommand(params martini.Params, r render.Render, // AgentSeed completely seeds a host with another host's snapshots. This is a complex operation // governed by orchestrator and executed by the two agents involved. -func (this *HttpAPI) AgentSeed(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AgentSeed(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2664,8 +2665,8 @@ func (this *HttpAPI) AgentSeed(params martini.Params, r render.Render, req *http } // AgentActiveSeeds lists active seeds and their state -func (this *HttpAPI) AgentActiveSeeds(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AgentActiveSeeds(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2685,8 +2686,8 @@ func (this *HttpAPI) AgentActiveSeeds(params martini.Params, r render.Render, re } // AgentRecentSeeds lists recent seeds of a given agent -func (this *HttpAPI) AgentRecentSeeds(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AgentRecentSeeds(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2706,8 +2707,8 @@ func (this *HttpAPI) AgentRecentSeeds(params martini.Params, r render.Render, re } // AgentSeedDetails provides details of a given seed -func (this *HttpAPI) AgentSeedDetails(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AgentSeedDetails(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2728,8 +2729,8 @@ func (this *HttpAPI) AgentSeedDetails(params martini.Params, r render.Render, re } // AgentSeedStates returns the breakdown of states (steps) of a given seed -func (this *HttpAPI) AgentSeedStates(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AgentSeedStates(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2750,8 +2751,8 @@ func (this *HttpAPI) AgentSeedStates(params martini.Params, r render.Render, req } // Seeds retruns all recent seeds -func (this *HttpAPI) Seeds(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) Seeds(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2771,8 +2772,8 @@ func (this *HttpAPI) Seeds(params martini.Params, r render.Render, req *http.Req } // AbortSeed instructs agents to abort an active seed -func (this *HttpAPI) AbortSeed(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AbortSeed(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2843,8 +2844,8 @@ func (this *HttpAPI) StatusCheck(params martini.Params, r render.Render, req *ht } // GrabElection forcibly grabs leadership. Use with care!! -func (this *HttpAPI) GrabElection(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) GrabElection(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2858,8 +2859,8 @@ func (this *HttpAPI) GrabElection(params martini.Params, r render.Render, req *h } // Reelect causes re-elections for an active node -func (this *HttpAPI) Reelect(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) Reelect(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2873,8 +2874,8 @@ func (this *HttpAPI) Reelect(params martini.Params, r render.Render, req *http.R } // RaftAddPeer adds a new node to the raft cluster -func (this *HttpAPI) RaftAddPeer(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RaftAddPeer(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2893,8 +2894,8 @@ func (this *HttpAPI) RaftAddPeer(params martini.Params, r render.Render, req *ht } // RaftAddPeer removes a node fro the raft cluster -func (this *HttpAPI) RaftRemovePeer(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RaftRemovePeer(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2913,8 +2914,8 @@ func (this *HttpAPI) RaftRemovePeer(params martini.Params, r render.Render, req } // RaftYield yields to a specified host -func (this *HttpAPI) RaftYield(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RaftYield(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -2927,8 +2928,8 @@ func (this *HttpAPI) RaftYield(params martini.Params, r render.Render, req *http } // RaftYieldHint yields to a host whose name contains given hint (e.g. DC) -func (this *HttpAPI) RaftYieldHint(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RaftYieldHint(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -3055,8 +3056,8 @@ func (this *HttpAPI) RaftSnapshot(params martini.Params, r render.Render, req *h } // ReloadConfiguration reloads confiug settings (not all of which will apply after change) -func (this *HttpAPI) ReloadConfiguration(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) ReloadConfiguration(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -3124,14 +3125,14 @@ func (this *HttpAPI) ReplicationAnalysisForKey(params martini.Params, r render.R } // RecoverLite attempts recovery on a given instance, without executing external processes -func (this *HttpAPI) RecoverLite(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpAPI) RecoverLite(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { params["skipProcesses"] = "true" - this.Recover(params, r, req, user) + this.Recover(params, r, req, user, s) } // Recover attempts recovery on a given instance -func (this *HttpAPI) Recover(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) Recover(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -3163,8 +3164,8 @@ func (this *HttpAPI) Recover(params martini.Params, r render.Render, req *http.R } // GracefulMasterTakeover gracefully fails over a master onto its single replica. -func (this *HttpAPI) gracefulMasterTakeover(params martini.Params, r render.Render, req *http.Request, user auth.User, auto bool) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) gracefulMasterTakeover(params martini.Params, r render.Render, req *http.Request, user auth.User, auto bool, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -3190,18 +3191,18 @@ func (this *HttpAPI) gracefulMasterTakeover(params martini.Params, r render.Rend // GracefulMasterTakeover gracefully fails over a master, either: // - onto its single replica, or // - onto a replica indicated by the user -func (this *HttpAPI) GracefulMasterTakeover(params martini.Params, r render.Render, req *http.Request, user auth.User) { - this.gracefulMasterTakeover(params, r, req, user, false) +func (this *HttpAPI) GracefulMasterTakeover(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.gracefulMasterTakeover(params, r, req, user, false, s) } // GracefulMasterTakeoverAuto gracefully fails over a master onto a replica of orchestrator's choosing -func (this *HttpAPI) GracefulMasterTakeoverAuto(params martini.Params, r render.Render, req *http.Request, user auth.User) { - this.gracefulMasterTakeover(params, r, req, user, true) +func (this *HttpAPI) GracefulMasterTakeoverAuto(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.gracefulMasterTakeover(params, r, req, user, true, s) } // ForceMasterFailover fails over a master (even if there's no particular problem with the master) -func (this *HttpAPI) ForceMasterFailover(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) ForceMasterFailover(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -3223,8 +3224,8 @@ func (this *HttpAPI) ForceMasterFailover(params martini.Params, r render.Render, } // ForceMasterTakeover fails over a master (even if there's no particular problem with the master) -func (this *HttpAPI) ForceMasterTakeover(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) ForceMasterTakeover(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -3257,8 +3258,8 @@ func (this *HttpAPI) ForceMasterTakeover(params martini.Params, r render.Render, } // Registers promotion preference for given instance -func (this *HttpAPI) RegisterCandidate(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) RegisterCandidate(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -3418,8 +3419,8 @@ func (this *HttpAPI) RecentlyActiveInstanceRecovery(params martini.Params, r ren } // ClusterInfo provides details of a given cluster -func (this *HttpAPI) AcknowledgeClusterRecoveries(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AcknowledgeClusterRecoveries(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -3442,7 +3443,7 @@ func (this *HttpAPI) AcknowledgeClusterRecoveries(params martini.Params, r rende Respond(r, &APIResponse{Code: ERROR, Message: fmt.Sprintf("No acknowledge comment given")}) return } - userId := getUserId(req, user) + userId := getUserId(req, user, s) if userId == "" { userId = inst.GetMaintenanceOwner() } @@ -3462,8 +3463,8 @@ func (this *HttpAPI) AcknowledgeClusterRecoveries(params martini.Params, r rende } // ClusterInfo provides details of a given cluster -func (this *HttpAPI) AcknowledgeInstanceRecoveries(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AcknowledgeInstanceRecoveries(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -3479,7 +3480,7 @@ func (this *HttpAPI) AcknowledgeInstanceRecoveries(params martini.Params, r rend Respond(r, &APIResponse{Code: ERROR, Message: fmt.Sprintf("No acknowledge comment given")}) return } - userId := getUserId(req, user) + userId := getUserId(req, user, s) if userId == "" { userId = inst.GetMaintenanceOwner() } @@ -3499,8 +3500,8 @@ func (this *HttpAPI) AcknowledgeInstanceRecoveries(params martini.Params, r rend } // ClusterInfo provides details of a given cluster -func (this *HttpAPI) AcknowledgeRecovery(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AcknowledgeRecovery(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -3525,7 +3526,7 @@ func (this *HttpAPI) AcknowledgeRecovery(params martini.Params, r render.Render, Respond(r, &APIResponse{Code: ERROR, Message: fmt.Sprintf("No acknowledge comment given")}) return } - userId := getUserId(req, user) + userId := getUserId(req, user, s) if userId == "" { userId = inst.GetMaintenanceOwner() } @@ -3551,8 +3552,8 @@ func (this *HttpAPI) AcknowledgeRecovery(params martini.Params, r render.Render, } // ClusterInfo provides details of a given cluster -func (this *HttpAPI) AcknowledgeAllRecoveries(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) AcknowledgeAllRecoveries(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -3562,7 +3563,7 @@ func (this *HttpAPI) AcknowledgeAllRecoveries(params martini.Params, r render.Re Respond(r, &APIResponse{Code: ERROR, Message: fmt.Sprintf("No acknowledge comment given")}) return } - userId := getUserId(req, user) + userId := getUserId(req, user, s) if userId == "" { userId = inst.GetMaintenanceOwner() } @@ -3595,8 +3596,8 @@ func (this *HttpAPI) BlockedRecoveries(params martini.Params, r render.Render, r } // DisableGlobalRecoveries globally disables recoveries -func (this *HttpAPI) DisableGlobalRecoveries(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) DisableGlobalRecoveries(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } @@ -3617,8 +3618,8 @@ func (this *HttpAPI) DisableGlobalRecoveries(params martini.Params, r render.Ren } // EnableGlobalRecoveries globally enables recoveries -func (this *HttpAPI) EnableGlobalRecoveries(params martini.Params, r render.Render, req *http.Request, user auth.User) { - if !isAuthorizedForAction(req, user) { +func (this *HttpAPI) EnableGlobalRecoveries(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + if !isAuthorizedForAction(req, user, s) { Respond(r, &APIResponse{Code: ERROR, Message: "Unauthorized"}) return } diff --git a/go/http/httpbase.go b/go/http/httpbase.go index 5bc21fe5c..84e499651 100644 --- a/go/http/httpbase.go +++ b/go/http/httpbase.go @@ -22,12 +22,14 @@ import ( "strings" "github.com/martini-contrib/auth" + "github.com/martini-contrib/sessions" + "github.com/openark/orchestrator/go/azure" "github.com/openark/orchestrator/go/config" "github.com/openark/orchestrator/go/inst" "github.com/openark/orchestrator/go/os" "github.com/openark/orchestrator/go/process" - "github.com/openark/orchestrator/go/raft" + orcraft "github.com/openark/orchestrator/go/raft" ) func getProxyAuthUser(req *http.Request) string { @@ -39,7 +41,7 @@ func getProxyAuthUser(req *http.Request) string { // isAuthorizedForAction checks req to see whether authenticated user has write-privileges. // This depends on configured authentication method. -func isAuthorizedForAction(req *http.Request, user auth.User) bool { +func isAuthorizedForAction(req *http.Request, user auth.User, session sessions.Session) bool { if config.Config.ReadOnly { return false } @@ -64,6 +66,18 @@ func isAuthorizedForAction(req *http.Request, user auth.User) bool { // passed authentication ==> writeable return true } + case "azure": + { + if azure.IsUserLogged(session) && + ((config.Config.AzureAdminRole != "" && + azure.GetUserRole(session) == config.Config.AzureAdminRole) || + config.Config.AzureAdminRole == "" && azure.GetUserRole(session) == "admin") { + // passed authentication ==> writeable + return true + } + // read only + return false + } case "proxy": { authUser := getProxyAuthUser(req) @@ -114,7 +128,7 @@ func authenticateToken(publicToken string, resp http.ResponseWriter) error { } // getUserId returns the authenticated user id, if available, depending on authertication method. -func getUserId(req *http.Request, user auth.User) string { +func getUserId(req *http.Request, user auth.User, session sessions.Session) string { if config.Config.ReadOnly { return "" } @@ -128,6 +142,14 @@ func getUserId(req *http.Request, user auth.User) string { { return string(user) } + case "azure": + { + if azure.IsUserLogged(session) { + return azure.GetUsername(session) + } else { + return "" + } + } case "proxy": { return getProxyAuthUser(req) diff --git a/go/http/web.go b/go/http/web.go index 575d3fd89..659239213 100644 --- a/go/http/web.go +++ b/go/http/web.go @@ -22,14 +22,18 @@ import ( "net/http" "net/http/pprof" "strconv" + "strings" "text/template" "github.com/go-martini/martini" "github.com/martini-contrib/auth" "github.com/martini-contrib/render" + "github.com/martini-contrib/sessions" "github.com/rcrowley/go-metrics" "github.com/rcrowley/go-metrics/exp" + "github.com/openark/golib/log" + "github.com/openark/orchestrator/go/azure" "github.com/openark/orchestrator/go/config" "github.com/openark/orchestrator/go/inst" ) @@ -51,6 +55,13 @@ func (this *HttpWeb) getInstanceKey(host string, port string) (inst.InstanceKey, return instanceKey, err } +func (this *HttpWeb) redirectOnAzureAuthentication(r render.Render, session sessions.Session) { + // If we are using Azure Authentication, we need to check is user in connected or not. + if config.Config.AuthenticationMethod == "azure" && !azure.IsUserLogged(session) { + r.Redirect(this.URLPrefix + "/web/auth") + } +} + func (this *HttpWeb) AccessToken(params martini.Params, r render.Render, req *http.Request, resp http.ResponseWriter, user auth.User) { publicToken := template.JSEscapeString(req.URL.Query().Get("publicToken")) err := authenticateToken(publicToken, resp) @@ -61,40 +72,48 @@ func (this *HttpWeb) AccessToken(params martini.Params, r render.Render, req *ht r.Redirect(this.URLPrefix + "/") } -func (this *HttpWeb) Index(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) Index(params martini.Params, r render.Render, req *http.Request, user auth.User, session sessions.Session) { // Redirect index so that all web URLs begin with "/web/". + this.redirectOnAzureAuthentication(r, session) + // We also redirect /web/ to /web/clusters so that // the Clusters page has a single canonical URL. r.Redirect(this.URLPrefix + "/web/clusters") } -func (this *HttpWeb) Clusters(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) Clusters(params martini.Params, r render.Render, req *http.Request, user auth.User, session sessions.Session) { + this.redirectOnAzureAuthentication(r, session) + r.HTML(200, "templates/clusters", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "clusters", "autoshow_problems": false, - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, session), + "userId": getUserId(req, user, session), "removeTextFromHostnameDisplay": config.Config.RemoveTextFromHostnameDisplay, "prefix": this.URLPrefix, "webMessage": config.Config.WebMessage, }) } -func (this *HttpWeb) ClustersAnalysis(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) ClustersAnalysis(params martini.Params, r render.Render, req *http.Request, user auth.User, session sessions.Session) { + this.redirectOnAzureAuthentication(r, session) + r.HTML(200, "templates/clusters_analysis", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "clusters", "autoshow_problems": false, - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, session), + "userId": getUserId(req, user, session), "removeTextFromHostnameDisplay": config.Config.RemoveTextFromHostnameDisplay, "prefix": this.URLPrefix, "webMessage": config.Config.WebMessage, }) } -func (this *HttpWeb) Cluster(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) Cluster(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) + clusterName, _ := figureClusterName(params["clusterName"]) r.HTML(200, "templates/cluster", map[string]interface{}{ @@ -104,8 +123,8 @@ func (this *HttpWeb) Cluster(params martini.Params, r render.Render, req *http.R "autoshow_problems": true, "contextMenuVisible": true, "pseudoGTIDModeEnabled": (config.Config.PseudoGTIDPattern != ""), - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "removeTextFromHostnameDisplay": config.Config.RemoveTextFromHostnameDisplay, "compactDisplay": template.JSEscapeString(req.URL.Query().Get("compact")), "prefix": this.URLPrefix, @@ -113,7 +132,9 @@ func (this *HttpWeb) Cluster(params martini.Params, r render.Render, req *http.R }) } -func (this *HttpWeb) ClusterByAlias(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) ClusterByAlias(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) + clusterName, err := inst.GetClusterByAlias(params["clusterAlias"]) // Willing to accept the case of multiple clusters; we just present one if clusterName == "" && err != nil { @@ -122,10 +143,12 @@ func (this *HttpWeb) ClusterByAlias(params martini.Params, r render.Render, req } params["clusterName"] = clusterName - this.Cluster(params, r, req, user) + this.Cluster(params, r, req, user, s) } -func (this *HttpWeb) ClusterByInstance(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) ClusterByInstance(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) + instanceKey, err := this.getInstanceKey(params["host"], params["port"]) if err != nil { r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()}) @@ -144,10 +167,12 @@ func (this *HttpWeb) ClusterByInstance(params martini.Params, r render.Render, r } params["clusterName"] = instance.ClusterName - this.Cluster(params, r, req, user) + this.Cluster(params, r, req, user, s) } -func (this *HttpWeb) ClusterPools(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) ClusterPools(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) + clusterName, _ := figureClusterName(params["clusterName"]) r.HTML(200, "templates/cluster_pools", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, @@ -156,8 +181,8 @@ func (this *HttpWeb) ClusterPools(params martini.Params, r render.Render, req *h "autoshow_problems": false, // because pool screen by default expands all hosts "contextMenuVisible": true, "pseudoGTIDModeEnabled": (config.Config.PseudoGTIDPattern != ""), - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "removeTextFromHostnameDisplay": config.Config.RemoveTextFromHostnameDisplay, "compactDisplay": template.JSEscapeString(req.URL.Query().Get("compact")), "prefix": this.URLPrefix, @@ -165,7 +190,9 @@ func (this *HttpWeb) ClusterPools(params martini.Params, r render.Render, req *h }) } -func (this *HttpWeb) Search(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) Search(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) + searchString := params["searchString"] if searchString == "" { searchString = req.URL.Query().Get("s") @@ -175,28 +202,31 @@ func (this *HttpWeb) Search(params martini.Params, r render.Render, req *http.Re "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "search", "searchString": searchString, - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "prefix": this.URLPrefix, "webMessage": config.Config.WebMessage, }) } -func (this *HttpWeb) Discover(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) Discover(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) r.HTML(200, "templates/discover", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "discover", - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "prefix": this.URLPrefix, "webMessage": config.Config.WebMessage, }) } -func (this *HttpWeb) Audit(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) Audit(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) + page, err := strconv.Atoi(params["page"]) if err != nil { page = 0 @@ -205,8 +235,8 @@ func (this *HttpWeb) Audit(params martini.Params, r render.Render, req *http.Req r.HTML(200, "templates/audit", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "audit", - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "page": page, "auditHostname": params["host"], @@ -216,7 +246,9 @@ func (this *HttpWeb) Audit(params martini.Params, r render.Render, req *http.Req }) } -func (this *HttpWeb) AuditRecovery(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) AuditRecovery(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) + page, err := strconv.Atoi(params["page"]) if err != nil { page = 0 @@ -232,8 +264,8 @@ func (this *HttpWeb) AuditRecovery(params martini.Params, r render.Render, req * r.HTML(200, "templates/audit_recovery", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "audit-recovery", - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "page": page, "clusterName": clusterName, @@ -245,7 +277,9 @@ func (this *HttpWeb) AuditRecovery(params martini.Params, r render.Render, req * }) } -func (this *HttpWeb) AuditFailureDetection(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) AuditFailureDetection(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) + page, err := strconv.Atoi(params["page"]) if err != nil { page = 0 @@ -259,8 +293,8 @@ func (this *HttpWeb) AuditFailureDetection(params martini.Params, r render.Rende r.HTML(200, "templates/audit_failure_detection", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "audit-failure-detection", - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "page": page, "detectionId": detectionId, @@ -270,24 +304,28 @@ func (this *HttpWeb) AuditFailureDetection(params martini.Params, r render.Rende }) } -func (this *HttpWeb) Agents(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) Agents(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) + r.HTML(200, "templates/agents", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "agents", - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "prefix": this.URLPrefix, "webMessage": config.Config.WebMessage, }) } -func (this *HttpWeb) Agent(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) Agent(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) + r.HTML(200, "templates/agent", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "agent", - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "agentHost": params["host"], "prefix": this.URLPrefix, @@ -295,12 +333,14 @@ func (this *HttpWeb) Agent(params martini.Params, r render.Render, req *http.Req }) } -func (this *HttpWeb) AgentSeedDetails(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) AgentSeedDetails(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) + r.HTML(200, "templates/agent_seed_details", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "agent seed details", - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "seedId": params["seedId"], "prefix": this.URLPrefix, @@ -308,83 +348,158 @@ func (this *HttpWeb) AgentSeedDetails(params martini.Params, r render.Render, re }) } -func (this *HttpWeb) Seeds(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) Seeds(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) + r.HTML(200, "templates/seeds", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "seeds", - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "prefix": this.URLPrefix, "webMessage": config.Config.WebMessage, }) } -func (this *HttpWeb) Home(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) Home(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) r.HTML(200, "templates/home", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "home", - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "prefix": this.URLPrefix, "webMessage": config.Config.WebMessage, }) } -func (this *HttpWeb) About(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) About(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) r.HTML(200, "templates/about", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "about", - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "prefix": this.URLPrefix, "webMessage": config.Config.WebMessage, }) } -func (this *HttpWeb) KeepCalm(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) KeepCalm(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) r.HTML(200, "templates/keep-calm", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "Keep Calm", - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "prefix": this.URLPrefix, "webMessage": config.Config.WebMessage, }) } -func (this *HttpWeb) FAQ(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) FAQ(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) r.HTML(200, "templates/faq", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "FAQ", - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "prefix": this.URLPrefix, "webMessage": config.Config.WebMessage, }) } -func (this *HttpWeb) Status(params martini.Params, r render.Render, req *http.Request, user auth.User) { +func (this *HttpWeb) Status(params martini.Params, r render.Render, req *http.Request, user auth.User, s sessions.Session) { + this.redirectOnAzureAuthentication(r, s) r.HTML(200, "templates/status", map[string]interface{}{ "agentsHttpActive": config.Config.ServeAgentsHttp, "title": "status", - "authorizedForAction": isAuthorizedForAction(req, user), - "userId": getUserId(req, user), + "authorizedForAction": isAuthorizedForAction(req, user, s), + "userId": getUserId(req, user, s), "autoshow_problems": false, "prefix": this.URLPrefix, "webMessage": config.Config.WebMessage, }) } +func (this *HttpWeb) AuthPage(params martini.Params, r render.Render, req *http.Request, session sessions.Session) { + if azure.IsUserLogged(session) { + r.Redirect(this.URLPrefix + "/") + } + + // Get query parameter 'error' in case of redirect error + errorParam := req.URL.Query().Get("error") + + r.HTML(200, "templates/auth", map[string]interface{}{ + "title": "Azure Authentication", + "prefix": this.URLPrefix, + "error": errorParam, + }, render.HTMLOptions{Layout: ""}) +} + +func (this *HttpWeb) Login(params martini.Params, r render.Render, req *http.Request, user auth.User, session sessions.Session) { + // Get credentials from form + email := req.FormValue("email") + pwd := req.FormValue("password") + + // Connect to Azure + graphHelper := azure.NewGraphHelper() + err := graphHelper.InitializeGraphForAppAuth(email, pwd) + if err != nil { + log.Fatal("Error initializing Graph for app auth: ", err) + } + fmt.Println("Success Login to Azure Application") + + guser := azure.GetAzureUser(&graphHelper) + + if guser != nil { + userRole := azure.GetAzureUserAppRoleAssignments(guser, &graphHelper) + + // Everything is good, we have all the needed information. + // We can now disconnect from azure to be sure + graphHelper.Clear() + + if *guser.GetDisplayName() != "" && userRole != "" { + session.Set("Username", *guser.GetDisplayName()) + session.Set("Role", userRole) + + r.Redirect(this.URLPrefix + "/") + } else { + r.Redirect(this.URLPrefix + "/web/auth?error=login") + } + } else { + // There was an error. Disconnect from azure + graphHelper.Clear() + r.Redirect(this.URLPrefix + "/web/auth?error=login") + } +} + +func (this *HttpWeb) Logout(params martini.Params, r render.Render, req *http.Request, user auth.User, res http.ResponseWriter, session sessions.Session) { + // Need to find a way to redirect to "/" if not logged + if res.Header().Get("Authorization") == "" { + switch strings.ToLower(config.Config.AuthenticationMethod) { + case "azure": + session.Clear() + r.Redirect(this.URLPrefix + "/") + default: + res.Header().Set("WWW-Authenticate", "Basic realm=\""+auth.BasicRealm+"\"") + http.Error(res, "Not Authorized", http.StatusUnauthorized) + } + } else { + r.Redirect(this.URLPrefix + "/") + } +} + func (this *HttpWeb) registerWebRequest(m *martini.ClassicMartini, path string, handler martini.Handler) { fullPath := fmt.Sprintf("%s/web/%s", this.URLPrefix, path) if path == "/" { @@ -398,6 +513,19 @@ func (this *HttpWeb) registerWebRequest(m *martini.ClassicMartini, path string, } } +func (this *HttpWeb) registerPostWebRequest(m *martini.ClassicMartini, path string, handler martini.Handler) { + fullPath := fmt.Sprintf("%s/web/%s", this.URLPrefix, path) + if path == "/" { + fullPath = fmt.Sprintf("%s/", this.URLPrefix) + } + + if config.Config.RaftEnabled { + m.Post(fullPath, raftReverseProxy, handler) + } else { + m.Post(fullPath, handler) + } +} + // RegisterRequests makes for the de-facto list of known Web calls func (this *HttpWeb) RegisterRequests(m *martini.ClassicMartini) { this.registerWebRequest(m, "access-token", this.AccessToken) @@ -439,6 +567,9 @@ func (this *HttpWeb) RegisterRequests(m *martini.ClassicMartini) { this.registerWebRequest(m, "agent/:host", this.Agent) this.registerWebRequest(m, "seed-details/:seedId", this.AgentSeedDetails) this.registerWebRequest(m, "seeds", this.Seeds) + this.registerWebRequest(m, "logout", this.Logout) + this.registerWebRequest(m, "auth", this.AuthPage) + this.registerPostWebRequest(m, "login/azure", this.Login) this.RegisterDebug(m) } diff --git a/resources/public/css/auth.css b/resources/public/css/auth.css new file mode 100644 index 000000000..2c92a1c12 --- /dev/null +++ b/resources/public/css/auth.css @@ -0,0 +1,135 @@ +* { + box-sizing: border-box; +} + +html { + height: 100%; +} + +body { + background-color: #354152; + color: #7e8ba3; + font: 300 1rem/1.5 Helvetica Neue, sans-serif; + margin: 0; + min-height: 100%; +} + +.align { + align-items: center; + display: flex; + flex-direction: row; +} + +.align__item--start { + align-self: flex-start; +} + +.align__item--end { + align-self: flex-end; +} + +.auth__logo, +.auth__title { + display: block; + margin-left: auto; + margin-right: auto; + margin-bottom: 2rem; +} + +.auth__logo { + animation: rotation 6s infinite linear; +} + +@keyframes rotation { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.grid { + margin: 0 auto; + max-width: 25rem; + width: 100%; +} + +svg { + height: auto; + max-width: 100%; + vertical-align: middle; +} + +a { + color: #7e8ba3; +} + +.register { + background-color: #1b3041; + box-shadow: 0 0 250px #000; + text-align: center; + padding: 4rem 2rem; +} + +p.error { + background-color: rgba(255, 0, 0, .6); + border: 1px solid white; + color: white; + font-weight: bold; + border-radius: 5px; + margin: 0 2em; + padding: 5px 0; +} + +.log-form { + width: 40%; + min-width: 320px; + max-width: 475px; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + -moz-transform: translate(-50%, -50%); + -o-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} + +.form { + display: block; + width: 100%; +} + +.form__group { + position: relative; + margin-top: 2rem; + margin-bottom: 2rem; +} + +.form__control { + background: none; + border: 2px solid #3498db; + padding: 10px; + width: 90%; + outline: none; + color: white; + font-size: 1rem; + font-family: "Montserrat", sans-serif; + border-radius: 24px; + transition: 0.25s; + margin-bottom: 10px; +} + +.form__control:focus, +.form__control:valid { + border-color: #2ecc71; +} + +.btn { + display: inline-block; + background: transparent; + border: 1px solid transparent; + padding: 0.5em 2em; + margin-right: 0.5em; +} \ No newline at end of file diff --git a/resources/public/css/custom.css b/resources/public/css/custom.css index e69de29bb..5e5414600 100644 --- a/resources/public/css/custom.css +++ b/resources/public/css/custom.css @@ -0,0 +1,17 @@ +.dropdown .dropdown-menu .nav-header { + padding-right: 20px; + padding-left: 20px; +} +.nav li + .nav-header { + margin-top: 9px; +} +.nav-header { + display: block; + padding: 3px 15px; + font-size: 11px; + font-weight: bold; + line-height: 20px; + color: #999999; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-transform: uppercase; +} \ No newline at end of file diff --git a/resources/public/images/auth-logo.png b/resources/public/images/auth-logo.png new file mode 100644 index 000000000..c5e84aeeb Binary files /dev/null and b/resources/public/images/auth-logo.png differ diff --git a/resources/public/images/auth-title.png b/resources/public/images/auth-title.png new file mode 100644 index 000000000..d25c0f1e4 Binary files /dev/null and b/resources/public/images/auth-title.png differ diff --git a/resources/public/images/orchestrator-logo-auth.png b/resources/public/images/orchestrator-logo-auth.png new file mode 100644 index 000000000..1e0b0938b Binary files /dev/null and b/resources/public/images/orchestrator-logo-auth.png differ diff --git a/resources/public/js/orchestrator.js b/resources/public/js/orchestrator.js index b00f11f88..d3a86680b 100644 --- a/resources/public/js/orchestrator.js +++ b/resources/public/js/orchestrator.js @@ -1143,8 +1143,10 @@ $(document).ready(function() { $("[data-nav-page=read-only]").css('display', 'inline-block'); } if (getUserId() != "" && !isAnonymized()) { + var userStatus = (isAuthorizedForAction() ? "admin" : "read only"); $("[data-nav-page=user-id]").css('display', 'inline-block'); - $("[data-nav-page=user-id] a").html(" " + getUserId()); + $("[data-nav-page=user-id] a:first").html(" " + getUserId() + " "); + $("[data-nav-page=user-id] li#user-role").html(userStatus); } var orchestratorMsg = sanitizeHTML(getParameterByName("orchestrator-msg")) if (orchestratorMsg) { diff --git a/resources/templates/auth.tmpl b/resources/templates/auth.tmpl new file mode 100644 index 000000000..a9fb15005 --- /dev/null +++ b/resources/templates/auth.tmpl @@ -0,0 +1,105 @@ + + + + + + + + + + Orchestrator - {{.title}} + + + +
+
+ + logo + {{ if and (eq .error "login")}} +

+ An error occurred. Please try again. +

+ {{ end}} +

+

+
+ +
+
+ +
+ +
+

+
+
+ + \ No newline at end of file diff --git a/resources/templates/layout.tmpl b/resources/templates/layout.tmpl index 8ff9753f4..4bdf2a11e 100644 --- a/resources/templates/layout.tmpl +++ b/resources/templates/layout.tmpl @@ -118,8 +118,13 @@
  • -
  • - +