From fc4e1ae9e928b702bde35b68b47192d56fbe2b91 Mon Sep 17 00:00:00 2001 From: Bruno Michel Date: Mon, 12 Feb 2024 13:53:15 +0100 Subject: [PATCH] Remove dead code The mobile apps cozy-drive and cozy-banks were able to onboard a new Cozy instance. This was done with a flow involving the cloudery. Those apps are no longer used, and the flow is no longer supported by the cloudery, so we can clean the stack code. --- assets/scripts/check-deeplink.js | 135 ------------------------- assets/templates/authorize.html | 1 - docs/auth.md | 47 --------- model/oauth/client.go | 9 -- web/auth/auth.go | 1 - web/auth/auth_test.go | 166 ------------------------------- web/auth/oauth.go | 36 ------- web/instances/client.go | 12 +-- web/settings/context.go | 42 -------- web/settings/settings_test.go | 57 ----------- web/statik/statik.go | 125 +++++++++-------------- 11 files changed, 52 insertions(+), 579 deletions(-) delete mode 100644 assets/scripts/check-deeplink.js diff --git a/assets/scripts/check-deeplink.js b/assets/scripts/check-deeplink.js deleted file mode 100644 index df4773fafa5..00000000000 --- a/assets/scripts/check-deeplink.js +++ /dev/null @@ -1,135 +0,0 @@ -function _createHiddenIframe(target, uri) { - const iframe = document.createElement('iframe') - iframe.src = uri - iframe.id = 'hiddenIframe' - iframe.style.display = 'none' - target.appendChild(iframe) - return iframe -} - -function openUriWithHiddenFrame(uri, failCb) { - const timeout = setTimeout(function () { - failCb() - handler.remove() - }, 500) - - let iframe = document.getElementById('hiddenIframe') - if (!iframe) { - iframe = _createHiddenIframe(document.body, 'about:blank') - } - - var handler = window.addEventListener('blur', onBlur) - function onBlur() { - clearTimeout(timeout) - handler.remove() - } - - iframe.contentWindow.location.href = uri -} - -function openUriWithTimeoutHack(uri, failCb) { - const timeout = setTimeout(function () { - failCb() - handler.remove() - }, 500) - - // handle page running in an iframe (blur must be registered with top level window) - let target = window - while (target != target.parent) { - target = target.parent - } - - var handler = target.addEventListener('blur', onBlur) - function onBlur() { - if (handler) { - clearTimeout(timeout) - handler.remove() - } - } - - window.location = uri -} - -function openUriWithMsLaunchUri(uri, failCb) { - navigator.msLaunchUri(uri, undefined, failCb) -} - -function checkBrowser() { - const isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0 - const ua = navigator.userAgent.toLowerCase() - const isSafari = - (~ua.indexOf('safari') && !~ua.indexOf('chrome')) || - Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > - 0 - const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream - const isIOS122 = isIOS && (ua.includes('os 12_2') || ua.includes('os 12_3')) - return { - isOpera, - isFirefox: typeof InstallTrigger !== 'undefined', - isSafari, - isIOS, - isIOS122, - isChrome: !!window.chrome && !isOpera, - } -} - -function check(uri, failCb) { - if (navigator.msLaunchUri) { - // for IE and Edge in Win 8 and Win 10 - openUriWithMsLaunchUri(uri, failCb) - } else { - const browser = checkBrowser() - - if (browser.isChrome || (browser.isIOS && !browser.isIOS122)) { - openUriWithTimeoutHack(uri, failCb) - } else if ((browser.isSafari && !browser.isIOS122) || browser.isFirefox) { - openUriWithHiddenFrame(uri, failCb) - } else { - failCb() - } - } -} - -;(function (window) { - let params = new URLSearchParams(document.location.search.substring(1)) - - const fallbackUri = params.get('fallback_uri') - const form = document.getElementById('authorizeform') - form.addEventListener('submit', function (e) { - /* - We want to call manually the /authorize route - in order to get a JSON response containing the deeplink - to use. - So we need to create the request - */ - e.preventDefault() - const arr = [] - for (var i = 0; i < form.elements.length; i++) { - const el = form.elements[i] - arr.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(el.value)) - } - const bodyString = arr.join('&') - const action = form.action - /* - We check if the client can open the deeplink. If not, we force the - redirection to the fallbackURI - */ - - fetch(action, { - method: 'POST', - body: bodyString, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - Accept: 'application/json', - }, - }) - .then(function (response) { - return response.json() - }) - .then(function (json) { - check(json.deeplink, function () { - window.location.href = fallbackUri - }) - }) - }) -})(window) diff --git a/assets/templates/authorize.html b/assets/templates/authorize.html index c020d9da346..0b71ba314e8 100644 --- a/assets/templates/authorize.html +++ b/assets/templates/authorize.html @@ -99,6 +99,5 @@

{{t "Authorize Title" .Client.ClientName}} - {{if .HasFallback}}{{end}} diff --git a/docs/auth.md b/docs/auth.md index c89385a5819..7cbdf521f81 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -1002,53 +1002,6 @@ Content-Type: application/json } ``` -### POST /auth/secret_exchange - -This endpoint is designed to trade a `secret` for a client. It is useful when an -OAuth client had been previously generated by an external entity with an -onboarding secret. - -The only parameter needed is `secret`. - - -```http -POST /auth/secret_exchange HTTP/1.1 -Host: cozy.example.org -Accept: application/json -Content-Type: application/json - -{ - "secret": "myS3Cr3t!" -} -``` - -```http -HTTP/1.1 200 OK -Content-Type: application/json -``` - -```json -{ - "client_id": "cf60f07cd7e00d0c0f86cd3f29240477", - "client_secret": "NNSLTid18EATInQWyg2XGKd_vs0e3zUC", - "client_secret_expires_at": 0, - "redirect_uris": [ - "https://example.org/oauth/callback" - ], - "grant_types": [ - "authorization_code", - "refresh_token" - ], - "response_types": [ - "code" - ], - "client_name": "cozy-test-3", - "software_id": "github.com/cozy/cozy-test", - "notifications": null, - "onboarding_secret": "myS3Cr3t!" -} -``` - ### POST /auth/session_code This endpoint can be used by the flagship application in order to create a diff --git a/model/oauth/client.go b/model/oauth/client.go index 050d59b6112..b182f2c9e9a 100644 --- a/model/oauth/client.go +++ b/model/oauth/client.go @@ -105,11 +105,6 @@ type Client struct { CertifiedFromStore bool `json:"certified_from_store,omitempty"` CreatedAtOnboarding bool `json:"created_at_onboarding,omitempty"` - OnboardingSecret string `json:"onboarding_secret,omitempty"` - OnboardingApp string `json:"onboarding_app,omitempty"` - OnboardingPermissions string `json:"onboarding_permissions,omitempty"` - OnboardingState string `json:"onboarding_state,omitempty"` - Metadata *metadata.CozyMetadata `json:"cozyMetadata,omitempty"` } @@ -592,10 +587,6 @@ func (c *Client) Update(i *instance.Instance, old *Client) *ClientRegistrationEr c.GrantTypes = []string{"authorization_code", "refresh_token"} c.ResponseTypes = []string{"code"} c.AllowLoginScope = old.AllowLoginScope - c.OnboardingSecret = "" - c.OnboardingApp = "" - c.OnboardingPermissions = "" - c.OnboardingState = "" if c.ClientName != old.ClientName { if err := c.ensureClientNameUnicity(i); err != nil { diff --git a/web/auth/auth.go b/web/auth/auth.go index 7a32c18e4ef..eacc6cc2485 100644 --- a/web/auth/auth.go +++ b/web/auth/auth.go @@ -628,7 +628,6 @@ func Routes(router *echo.Group) { authHandler.Register(router.Group("/authorize", noCSRF)) router.POST("/access_token", accessToken) - router.POST("/secret_exchange", secretExchange) // Flagship app router.POST("/session_code", CreateSessionCode) diff --git a/web/auth/auth_test.go b/web/auth/auth_test.go index 8b5b16e40bf..4f41e4798cc 100644 --- a/web/auth/auth_test.go +++ b/web/auth/auth_test.go @@ -1081,75 +1081,6 @@ func TestAuth(t *testing.T) { assert.Equal(t, results[0].Scope, "files:read") }) - t.Run("AuthorizeSuccessOnboardingDeeplink", func(t *testing.T) { - e := testutils.CreateTestClient(t, ts.URL) - - var oauthClient oauth.Client - oauthClient.RedirectURIs = []string{"cozydrive://"} - oauthClient.ClientName = "cozy-test-install-app" - oauthClient.SoftwareID = "io.cozy.mobile.drive" - oauthClient.OnboardingSecret = "toto" - oauthClient.Create(testInstance) - - body := e.GET("/auth/authorize"). - WithQuery("response_type", "code"). - WithQuery("state", "123456"). - WithQuery("scope", "files:read"). - WithQuery("redirect_uri", "https://example.org/oauth/callback"). - WithQuery("client_id", clientID). - WithHost(domain). - WithCookie(session.CookieName(testInstance), sessionID). - Expect().Status(200). - ContentType("text/html", "utf-8"). - Body() - - body.Contains("would like permission to access your Cozy") - matches := body.Match(` 0 { - redirectURI = client.RedirectURIs[0] - } - - // Create and adding a fallbackURI in case of no-supporting custom - // protocol cozy:// - // Basically, it parses the app slug and computes the web app url - // Example: cozydrive:// => http://drive.alice.cozy.localhost:8080/ - r, err := url.Parse(redirectURI) - if err != nil { - return err - } - // If the redirectURI scheme is not starting with cozy://, it means - // that we probably are on a recent mobile, handling universal/android - // links. We won't provide a fallbackURI because the redirectURI should - // be enough to handle the redirection on the mobile-side - var fallbackURI string - if strings.HasPrefix(r.Scheme, "cozy") { - appSlug := strings.TrimPrefix(client.SoftwareID, "registry://") - fallbackURI = i.SubDomain(appSlug).String() - } - // Redirection - queryParams := url.Values{ - "client_id": {client.CouchID}, - "redirect_uri": {redirectURI}, - "state": {client.OnboardingState}, - "fallback_uri": {fallbackURI}, - "response_type": {"code"}, - "scope": {client.OnboardingPermissions}, - } - redirect = i.PageURL("/auth/authorize", queryParams) - } if acceptHTML { return c.Redirect(http.StatusSeeOther, redirect) } diff --git a/web/settings/settings_test.go b/web/settings/settings_test.go index eca46bfca8f..281586670c4 100644 --- a/web/settings/settings_test.go +++ b/web/settings/settings_test.go @@ -6,7 +6,6 @@ import ( "fmt" "net/http" "net/http/httptest" - "net/url" "testing" "time" @@ -1080,62 +1079,6 @@ func TestSettings(t *testing.T) { }) } -func TestRedirectOnboardingSecret(t *testing.T) { - if testing.Short() { - t.Skip("an instance is required for this test: test skipped due to the use of --short flag") - } - - config.UseTestFile(t) - testutils.NeedCouchdb(t) - setup := testutils.NewSetup(t, t.Name()) - - testInstance := setup.GetTestInstance(&lifecycle.Options{ - Locale: "en", - Timezone: "Europe/Berlin", - Email: "alice@example.com", - ContextName: "test-context", - }) - sessCookie := session.CookieName(testInstance) - - svc := csettings.NewServiceMock(t) - tsURL := setupRouter(t, testInstance, svc).URL - - e := testutils.CreateTestClient(t, tsURL) - - // Without onboarding - e.GET("/settings/onboarded"). - WithCookie(sessCookie, "connected"). - WithRedirectPolicy(httpexpect.DontFollowRedirects). - Expect().Status(303). - Header("Location").IsEqual(testInstance.OnboardedRedirection().String()) - - // With onboarding - deeplink := "cozydrive://testinstance.com" - oauthClient := &oauth.Client{ - RedirectURIs: []string{deeplink}, - ClientName: "CozyTest", - SoftwareID: "/github.com/cozy-labs/cozy-desktop", - OnboardingSecret: "foobar", - OnboardingApp: "test", - } - - oauthClient.Create(testInstance) - - redirectURL := e.GET("/settings/onboarded"). - WithCookie(sessCookie, "connected"). - WithRedirectPolicy(httpexpect.DontFollowRedirects). - Expect().Status(303). - Header("Location"). - NotEqual(testInstance.OnboardedRedirection().String()). - Contains("/auth/authorize").Raw() - - u, err := url.Parse(redirectURL) - require.NoError(t, err) - - values := u.Query() - assert.Equal(t, values.Get("redirect_uri"), deeplink) -} - func TestRegisterPassphraseForFlagshipApp(t *testing.T) { if testing.Short() { t.Skip("an instance is required for this test: test skipped due to the use of --short flag") diff --git a/web/statik/statik.go b/web/statik/statik.go index 64a71914911..0552557035c 100644 --- a/web/statik/statik.go +++ b/web/statik/statik.go @@ -38704,35 +38704,6 @@ Size: 26 iwyAVXNlci1hZ2VudDogKgpEaXNhbGxvdzogLwoD -----END COZY ASSET----- -----BEGIN COZY ASSET----- -Name: /scripts/check-deeplink.js -Size: 3638 - -GzUOAIzTFbOMm8Yti9X0z+c11YWob0WdixKjZxFLEIU+e1u+GpoaMSNqCP/bmX3S -7RW3cq6d12yTciUN2jAJIBYP6pQGBhCax1DtfdsAL4joUnuVoPx1jCGJ8AWHw0MH -Pg09o8wFjGOO3o8cT9tAEtExKYGepetZ6Bnw66YBRp0wGyKIJEJohedyRbTCVMCZ -BEheA/hMLBgsyd589sy0U0ilO1ldhJ6JKTVzDh3JmVdtGIBlYigsxwLgeEkqAOWb -9a1B/P0yJp9KKRHQb2ZdVtaHGcIcbCHX3DJg9SnNtMg/gLZiSApcpXg7itWBCJBw -xtgTlaiYLEpSDhcUY4dTCa6prF6ANSsCHVHJ7MvMl45gNKUwqZIgMDlsZID/yZmu -9+YrySSRmNZ8Og21FrtFyIxjB5/3FIC2AvtvhCPjKyUckGvlRytAwVcSubrGog9N -YhNAktgqzWqSvaU55JF+cp7P//RWtoxiTAXnM3352h+xbbJIzD11NzggVW2ursJQ -jKJlldaXWs75PdXHbG0tss4CP10sgtQK3TMv/3R795AkhR1GysevhhrsNNTWXRWB -f3vULe0ZcADIVagDcT7akcHRX1uhpph6qLBYCACeNqYOD2treXkrSZg2qaeoiDrK -Q53ceNF6KSJXu6l4/vj89hEsae50Y2GmNJ382tpG0oBIpRjRZsjL14/D3kU8vUpn -MuA/xcGODPV7Y3KXrI905jdDaRwDdjrr1wMQUvKyigH4+QDx/s8cHZJT1H22xow7 -0NElOWIgiB8vjLVdYqS+TkVqwBSTcj8uI0spjIk1dX9atEFiFNJ3WtbD+TH0sDEM -ytQMXl0JKn8VaZa9N4rSWnoOarCFfQdDXFYaoRJIVScyQLijCGVlvXaxuVarlLw3 -FLuhRhRNwLq23EmHYyTFIpT7GnimUZCeq9OwzqiXLO1Waxeq9xXzLcxhGont88OV -EYovyV7CTINh55uDp2mlREkRU1NMt+JZQ9B7sSRWJWEXf8OlOhLDL9vq8O6LK8Tm -KH+kqgrYnZOtl9qmKZBVJIeq16ffJs1VfDLnt0GniWR4DrY33flNaKnRCt35Z1u8 -kUai2NxiYcMHcXgc+s0EZPqxGA+ZqRN/0Bg286VI8tfBpsXcVeJ/T3se2Oc3eFbL -epAjrxfR5BSNcW3i4/nPfwK+faHZB7tY/jReFwgJjZCUaX7Y7CeGlt83iIKYEAUd -mocqkhq02IDoCXYclryK4Je0TSkFMxlHQKJMRflvYVrvcGqLuq6hQ678KIHzFoY2 -iBkPTjohoWdrXnPkQdmrPK8+nPMGWX7VDOodGaMmlqP48JMWUUOW6tkhqd1Kgi0O -rdE1v+LFNkBtqSL+1OxeVFCLaH0SH98U8aw4jodevy9KQ1oGxqGNOZRGudKVyz/q -pQEkzxfHVw1Wpj0Z0erKoouEpxDFbN+E0vcJTDKYOsGOoKQG5KNo/fZYt+i5u4yN -+WVnJW1X ------END COZY ASSET----- ------BEGIN COZY ASSET----- Name: /scripts/cirrus.js Size: 2106 @@ -39016,55 +38987,55 @@ GyYA6I2Uq/VkrfISJKYVQNzywAYcOBRoMug55RR0sU5DRqz0BhgC -----END COZY ASSET----- -----BEGIN COZY ASSET----- Name: /templates/authorize.html -Size: 7639 +Size: 7538 -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 +4KqvpAGMGA3Gvq0HHwA= -----END COZY ASSET----- -----BEGIN COZY ASSET----- Name: /templates/authorize_move.html