/* ************************************************************************* IT Mill Toolkit Development of Browser User Interfaces Made Easy Copyright (C) 2000-2006 IT Mill Ltd ************************************************************************* This product is distributed under commercial license that can be found from the product package on license.pdf. Use of this product might require purchasing a commercial license from IT Mill Ltd. For guidelines on usage, see licensing-guidelines.html ************************************************************************* For more information, contact: IT Mill Ltd phone: +358 2 4802 7180 Ruukinkatu 2-4 fax: +358 2 4802 7181 20540, Turku email: info@itmill.com Finland company www: www.itmill.com Primary source for information and releases: www.itmill.com ********************************************************************** */ package com.itmill.toolkit.ui; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.Locale; import java.util.Map; import java.util.Set; import com.itmill.toolkit.Application; import com.itmill.toolkit.event.EventRouter; import com.itmill.toolkit.event.MethodEventSource; import com.itmill.toolkit.terminal.ErrorMessage; import com.itmill.toolkit.terminal.PaintException; import com.itmill.toolkit.terminal.PaintTarget; import com.itmill.toolkit.terminal.Resource; import com.itmill.toolkit.terminal.VariableOwner; /** * An abstract class that defines default implementation for the * {@link Component} interface. Basic UI components that are not derived from an * external component can inherit this class to easily qualify as a IT Mill * Toolkit component. Most components in the toolkit do just that. * * @author IT Mill Ltd. * @version * @VERSION@ * @since 3.0 */ public abstract class AbstractComponent implements Component, MethodEventSource { /* Private members ************************************************* */ /** * Style names. */ private ArrayList styles; /** * Caption text. */ private String caption; /** * Application specific data object. */ private Object applicationData; /** * Icon to be shown together with caption. */ private Resource icon; /** * Is the component enable (its normal usage is allowed). */ private boolean enabled = true; /** * Is the component visible (it is rendered). */ private boolean visible = true; /** * Is the component read-only ? */ private boolean readOnly = false; /** * Description of the usage (XML). */ private String description = null; /** * The container this component resides in. */ private Component parent = null; /** * The EventRouter used for the event model. */ private EventRouter eventRouter = null; /** * The internal error message of the component. */ private ErrorMessage componentError = null; /** * Immediate mode: if true, all variable changes are required to be sent * from the terminal immediately. */ private boolean immediate = false; /** * Locale of this component. */ private Locale locale; /** * List of repaint request listeners or null if not listened at all. */ private LinkedList repaintRequestListeners = null; /** * Are all the repaint listeners notified about recent changes ? */ private boolean repaintRequestListenersNotified = false; /* Constructor ***************************************************** */ /** * Constructs a new Component. */ public AbstractComponent() { } /* Get/Set component properties ************************************ */ /** * Gets the UIDL tag corresponding to the component. * * @return the component's UIDL tag as String */ public abstract String getTag(); /** * Gets style for component. Multiple styles are joined with spaces. * * @return the component's styleValue of property style. * @deprecated Use getStyleName() instead; renamed for consistency and to * indicate that "style" should not be used to switch client * side implementation, only to style the component. */ public String getStyle() { return getStyleName(); } /** * Sets and replaces all previous style names of the component. This method * will trigger a * {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent RepaintRequestEvent}. * * @param style * the new style of the component. * @deprecated Use setStyleName() instead; renamed for consistency and to * indicate that "style" should not be used to switch client * side implementation, only to style the component. */ public void setStyle(String style) { setStyleName(style); } /* * Gets the component's style. Don't add a JavaDoc comment here, we use the * default documentation from implemented interface. */ public String getStyleName() { String s = ""; if (styles != null) { for (Iterator it = styles.iterator(); it.hasNext();) { s += (String) it.next(); if (it.hasNext()) { s += " "; } } } return s; } /* * Sets the component's style. Don't add a JavaDoc comment here, we use the * default documentation from implemented interface. */ public void setStyleName(String style) { if (style == null || "".equals(style)) { styles = null; requestRepaint(); return; } if (styles == null) { styles = new ArrayList(); } styles.clear(); styles.add(style); requestRepaint(); } public void addStyleName(String style) { if (style == null || "".equals(style)) { return; } if (styles == null) { styles = new ArrayList(); } if (!styles.contains(style)) { styles.add(style); requestRepaint(); } } public void removeStyleName(String style) { styles.remove(style); requestRepaint(); } /* * Get's the component's caption. Don't add a JavaDoc comment here, we use * the default documentation from implemented interface. */ public String getCaption() { return caption; } /** * Sets the component's caption String. Caption is the * visible name of the component. This method will trigger a * {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent RepaintRequestEvent}. * * @param caption * the new caption String for the component. */ public void setCaption(String caption) { this.caption = caption; requestRepaint(); } /* * Don't add a JavaDoc comment here, we use the default documentation from * implemented interface. */ public Locale getLocale() { if (locale != null) { return locale; } if (parent != null) { return parent.getLocale(); } Application app = getApplication(); if (app != null) { return app.getLocale(); } return null; } /** * Sets the locale of this component. * * @param locale * the locale to become this component's locale. */ public void setLocale(Locale locale) { this.locale = locale; } /* * Gets the component's icon resource. Don't add a JavaDoc comment here, we * use the default documentation from implemented interface. */ public Resource getIcon() { return icon; } /** * Sets the component's icon. This method will trigger a * {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent RepaintRequestEvent}. * * @param icon * the icon to be shown with the component's caption. */ public void setIcon(Resource icon) { this.icon = icon; requestRepaint(); } /* * Tests if the component is enabled or not. Don't add a JavaDoc comment * here, we use the default documentation from implemented interface. */ public boolean isEnabled() { return enabled && isVisible(); } /* * Enables or disables the component. Don't add a JavaDoc comment here, we * use the default documentation from implemented interface. */ public void setEnabled(boolean enabled) { if (this.enabled != enabled) { this.enabled = enabled; requestRepaint(); } } /* * Tests if the component is in the immediate mode. Don't add a JavaDoc * comment here, we use the default documentation from implemented * interface. */ public boolean isImmediate() { return immediate; } /** * Sets the component's immediate mode to the specified status. This method * will trigger a * {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent RepaintRequestEvent}. * * @param immediate * the boolean value specifying if the component should be in * the immediate mode after the call. * @see Component#isImmediate() */ public void setImmediate(boolean immediate) { this.immediate = immediate; requestRepaint(); } /* * Tests if the component is visible. Don't add a JavaDoc comment here, we * use the default documentation from implemented interface. */ public boolean isVisible() { return visible; } /* * Sets the components visibility. Don't add a JavaDoc comment here, we use * the default documentation from implemented interface. */ public void setVisible(boolean visible) { if (this.visible != visible) { this.visible = visible; // Instead of requesting repaint normally we // fire the event directly to assure that the // event goes through event in the component might // now be invisible fireRequestRepaintEvent(null); } } /** *

* Gets the component's description. The description can be used to briefly * describe the state of the component to the user. The description string * may contain certain XML tags: *

* *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
TagDescriptionExample
<b>boldbold text
<i>italicitalic text
<u>underlinedunderlined text
<br>linebreakN/A
<ul>
* <li>item1
* <li>item1
* </ul>
item list *
    *
  • item1 *
  • item2 *
*
*

* *

* These tags may be nested. *

* * @return component's description String */ public String getDescription() { return description; } /** * Sets the component's description. See {@link #getDescription()} for more * information on what the description is. This method will trigger a * {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent RepaintRequestEvent}. * * @param description * the new description string for the component. */ public void setDescription(String description) { this.description = description; requestRepaint(); } /* * Gets the component's parent component. Don't add a JavaDoc comment here, * we use the default documentation from implemented interface. */ public Component getParent() { return parent; } /* * Sets the parent component. Don't add a JavaDoc comment here, we use the * default documentation from implemented interface. */ public void setParent(Component parent) { // If the parent is not changed, dont do nothing if (parent == this.parent) { return; } // Send detach event if the component have been connected to a window if (getApplication() != null) { detach(); this.parent = null; } // Connect to new parent this.parent = parent; // Send attach event if connected to a window if (getApplication() != null) { attach(); } } /** * Gets the error message for this component. * * @return ErrorMessage containing the description of the error state of the * component or null, if the component contains no errors. Extending * classes should override this method if they support other error * message types such as validation errors or buffering errors. The * returned error message contains information about all the errors. */ public ErrorMessage getErrorMessage() { return componentError; } /** * Gets the component's error message. * * @link Terminal.ErrorMessage#ErrorMessage(String, int) * * @return the component's error message. */ public ErrorMessage getComponentError() { return componentError; } /** * Sets the component's error message. The message may contain certain XML * tags, for more information see * * @link Component.ErrorMessage#ErrorMessage(String, int) * * @param componentError * the new ErrorMessage of the component. */ public void setComponentError(ErrorMessage componentError) { this.componentError = componentError; fireComponentErrorEvent(); requestRepaint(); } /* * Tests if the component is in read-only mode. Don't add a JavaDoc comment * here, we use the default documentation from implemented interface. */ public boolean isReadOnly() { return readOnly; } /* * Sets the component's read-only mode. Don't add a JavaDoc comment here, we * use the default documentation from implemented interface. */ public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; requestRepaint(); } /* * Gets the parent window of the component. Don't add a JavaDoc comment * here, we use the default documentation from implemented interface. */ public Window getWindow() { if (parent == null) { return null; } else { return parent.getWindow(); } } /* * Notify the component that it's attached to a window. Don't add a JavaDoc * comment here, we use the default documentation from implemented * interface. */ public void attach() { requestRepaint(); } /* * Detach the component from application. Don't add a JavaDoc comment here, * we use the default documentation from implemented interface. */ public void detach() { } /* * Gets the parent application of the component. Don't add a JavaDoc comment * here, we use the default documentation from implemented interface. */ public Application getApplication() { if (parent == null) { return null; } else { return parent.getApplication(); } } /* Component painting ********************************************** */ /* Documented in super interface */ public void requestRepaintRequests() { repaintRequestListenersNotified = false; } /* * Paints the component into a UIDL stream. Don't add a JavaDoc comment * here, we use the default documentation from implemented interface. */ public final void paint(PaintTarget target) throws PaintException { if (!target.startTag(this, getTag()) || repaintRequestListenersNotified) { // Paint the contents of the component if (styles != null && styles.size() > 0) { target.addAttribute("style", getStyle()); } if (isReadOnly()) { target.addAttribute("readonly", true); } if (!isVisible()) { target.addAttribute("invisible", true); } if (isImmediate()) { target.addAttribute("immediate", true); } if (!isEnabled()) { target.addAttribute("disabled", true); } if (getCaption() != null) { target.addAttribute("caption", getCaption()); } if (getIcon() != null) { target.addAttribute("icon", getIcon()); } // Only paint content of visible components. if (isVisible()) { String desc = getDescription(); if (desc != null && description.length() > 0) { target.addAttribute("description", getDescription()); } paintContent(target); ErrorMessage error = getErrorMessage(); if (error != null) { error.paint(target); } } } else { // Contents have not changed, only cached presentation can be used target.addAttribute("cached", true); } target.endTag(getTag()); repaintRequestListenersNotified = false; } /** * Paints any needed component-specific things to the given UIDL stream. The * more general {@link #paint(PaintTarget)} method handles all general * attributes common to all components, and it calls this method to paint * any component-specific attributes to the UIDL stream. * * @param target * the target UIDL stream where the component should paint * itself to * @throws PaintException * if the paint operation failed. */ public void paintContent(PaintTarget target) throws PaintException { } /* Documentation copied from interface */ public void requestRepaint() { // The effect of the repaint request is identical to case where a // child requests repaint childRequestedRepaint(null); } /* Documentation copied from interface */ public void childRequestedRepaint(Collection alreadyNotified) { // Invisible components do not need repaints if (!isVisible()) { return; } fireRequestRepaintEvent(alreadyNotified); } /** * Fires the repaint request event. * * @param alreadyNotified */ private void fireRequestRepaintEvent(Collection alreadyNotified) { // Notify listeners only once if (!repaintRequestListenersNotified) { // Notify the listeners if (repaintRequestListeners != null && !repaintRequestListeners.isEmpty()) { Object[] listeners = repaintRequestListeners.toArray(); RepaintRequestEvent event = new RepaintRequestEvent(this); for (int i = 0; i < listeners.length; i++) { if (alreadyNotified == null) { alreadyNotified = new LinkedList(); } if (!alreadyNotified.contains(listeners[i])) { ((RepaintRequestListener) listeners[i]) .repaintRequested(event); alreadyNotified.add(listeners[i]); repaintRequestListenersNotified = true; } } } // Notify the parent Component parent = getParent(); if (parent != null) { parent.childRequestedRepaint(alreadyNotified); } } } /* Documentation copied from interface */ public void addListener(RepaintRequestListener listener) { if (repaintRequestListeners == null) { repaintRequestListeners = new LinkedList(); } if (!repaintRequestListeners.contains(listener)) { repaintRequestListeners.add(listener); } } /* Documentation copied from interface */ public void removeListener(RepaintRequestListener listener) { if (repaintRequestListeners != null) { repaintRequestListeners.remove(listener); if (repaintRequestListeners.isEmpty()) { repaintRequestListeners = null; } } } /* Component variable changes ************************************** */ /* * Invoked when the value of a variable has changed. Don't add a JavaDoc * comment here, we use the default documentation from implemented * interface. */ public void changeVariables(Object source, Map variables) { } /* Dependency -framework is deprecated */ public void dependsOn(VariableOwner depended) { } public void removeDirectDependency(VariableOwner depended) { } public Set getDirectDependencies() { return null; } /* General event framework *************************************** */ private static final Method COMPONENT_EVENT_METHOD; static { try { COMPONENT_EVENT_METHOD = Component.Listener.class .getDeclaredMethod("componentEvent", new Class[] { Component.Event.class }); } catch (java.lang.NoSuchMethodException e) { // This should never happen e.printStackTrace(); throw new java.lang.RuntimeException(); } } /** *

* Registers a new listener with the specified activation method to listen * events generated by this component. If the activation method does not * have any arguments the event object will not be passed to it when it's * called. *

* *

* For more information on the inheritable event mechanism see the * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}. *

* * @param eventType * the type of the listened event. Events of this type or its * subclasses activate the listener. * @param object * the object instance who owns the activation method. * @param method * the activation method. */ public void addListener(Class eventType, Object object, Method method) { if (eventRouter == null) { eventRouter = new EventRouter(); } eventRouter.addListener(eventType, object, method); } /** *

* Registers a new listener with the specified activation method to listen * events generated by this component. If the activation method does not * have any arguments the event object will not be passed to it when it's * called. *

* *

* This version of addListener gets the name of the * activation method as a parameter. The actual method is reflected from * object, and unless exactly one match is found, * java.lang.IllegalArgumentException is thrown. *

* *

* For more information on the inheritable event mechanism see the * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}. *

* * @param eventType * the type of the listened event. Events of this type or its * subclasses activate the listener. * @param object * the object instance who owns the activation method. * @param methodName * the name of the activation method. */ public void addListener(Class eventType, Object object, String methodName) { if (eventRouter == null) { eventRouter = new EventRouter(); } eventRouter.addListener(eventType, object, methodName); } /** * Removes all registered listeners matching the given parameters. Since * this method receives the event type and the listener object as * parameters, it will unregister all object's methods that * are registered to listen to events of type eventType * generated by this component. * *

* For more information on the inheritable event mechanism see the * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}. *

* * @param eventType * the exact event type the object listens to. * @param target * the target object that has registered to listen to events * of type eventType with one or more methods. */ public void removeListener(Class eventType, Object target) { if (eventRouter != null) { eventRouter.removeListener(eventType, target); } } /** * Removes one registered listener method. The given method owned by the * given object will no longer be called when the specified events are * generated by this component. * *

* For more information on the inheritable event mechanism see the * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}. *

* * @param eventType * the exact event type the object listens to. * @param target * target object that has registered to listen to events of * type eventType with one or more methods. * @param method * the method owned by target that's * registered to listen to events of type * eventType. */ public void removeListener(Class eventType, Object target, Method method) { if (eventRouter != null) { eventRouter.removeListener(eventType, target, method); } } /** *

* Removes one registered listener method. The given method owned by the * given object will no longer be called when the specified events are * generated by this component. *

* *

* This version of removeListener gets the name of the * activation method as a parameter. The actual method is reflected from * target, and unless exactly one match is found, * java.lang.IllegalArgumentException is thrown. *

* *

* For more information on the inheritable event mechanism see the * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}. *

* * @param eventType * the exact event type the object listens to. * @param target * the target object that has registered to listen to events * of type eventType with one or more methods. * @param methodName * the name of the method owned by target * that's registered to listen to events of type * eventType. */ public void removeListener(Class eventType, Object target, String methodName) { if (eventRouter != null) { eventRouter.removeListener(eventType, target, methodName); } } /** * Sends the event to all listeners. * * @param event * the Event to be sent to all listeners. */ protected void fireEvent(Component.Event event) { if (eventRouter != null) { eventRouter.fireEvent(event); } } /* Component event framework *************************************** */ /* * Registers a new listener to listen events generated by this component. * Don't add a JavaDoc comment here, we use the default documentation from * implemented interface. */ public void addListener(Component.Listener listener) { if (eventRouter == null) { eventRouter = new EventRouter(); } eventRouter.addListener(Component.Event.class, listener, COMPONENT_EVENT_METHOD); } /* * Removes a previously registered listener from this component. Don't add a * JavaDoc comment here, we use the default documentation from implemented * interface. */ public void removeListener(Component.Listener listener) { if (eventRouter != null) { eventRouter.removeListener(Component.Event.class, listener, COMPONENT_EVENT_METHOD); } } /** * Emits the component event. It is transmitted to all registered listeners * interested in such events. */ protected void fireComponentEvent() { fireEvent(new Component.Event(this)); } /** * Emits the component error event. It is transmitted to all registered * listeners interested in such events. */ protected void fireComponentErrorEvent() { fireEvent(new Component.ErrorEvent(getComponentError(), this)); } /** * Sets the application specific data object. * * @param data * the Application specific data. * @since 3.1 */ public void setData(Object data) { applicationData = data; } /** * Gets the application specific data. * * @return the Application specific data set with setData function. * @since 3.1 */ public Object getData() { return applicationData; } }e30'>backport/48044/stable30 Nextcloud server, a safe home for all your data: https://github.com/nextcloud/serverwww-data
aboutsummaryrefslogtreecommitdiffstats
path: root/lib/public/AppFramework/AuthPublicShareController.php
blob: 28a92fedcc9a1a40bb3a991bfe1d4e4c4bd1d5bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
<?php

declare(strict_types=1);

/**
 * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */
namespace OCP\AppFramework;

use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\Attribute\UseSession;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;

/**
 * Base controller for interactive public shares
 *
 * It will verify if the user is properly authenticated to the share. If not the
 * user will be redirected to an authentication page.
 *
 * Use this for a controller that is to be called directly by a user. So the
 * normal public share page for files/calendars etc.
 *
 * @since 14.0.0
 */
abstract class AuthPublicShareController extends PublicShareController {
	/** @var IURLGenerator */
	protected $urlGenerator;

	/**
	 * @since 14.0.0
	 */
	public function __construct(string $appName,
		IRequest $request,
		ISession $session,
		IURLGenerator $urlGenerator) {
		parent::__construct($appName, $request, $session);

		$this->urlGenerator = $urlGenerator;
	}

	/**
	 * Show the authentication page
	 * The form has to submit to the authenticate method route
	 *
	 * @since 14.0.0
	 */
	#[NoCSRFRequired]
	#[PublicPage]
	public function showAuthenticate(): TemplateResponse {
		return new TemplateResponse('core', 'publicshareauth', [], 'guest');
	}

	/**
	 * The template to show when authentication failed
	 *
	 * @since 14.0.0
	 */
	protected function showAuthFailed(): TemplateResponse {
		return new TemplateResponse('core', 'publicshareauth', ['wrongpw' => true], 'guest');
	}

	/**
	 * The template to show after user identification
	 *
	 * @since 24.0.0
	 */
	protected function showIdentificationResult(bool $success): TemplateResponse {
		return new TemplateResponse('core', 'publicshareauth', ['identityOk' => $success], 'guest');
	}

	/**
	 * Validates that the provided identity is allowed to receive a temporary password
	 *
	 * @since 24.0.0
	 */
	protected function validateIdentity(?string $identityToken = null): bool {
		return false;
	}

	/**
	 * Generates a password
	 *
	 * @since 24.0.0
	 */
	protected function generatePassword(): void {
	}

	/**
	 * Verify the password
	 *
	 * @since 24.0.0
	 */
	protected function verifyPassword(string $password): bool {
		return false;
	}

	/**
	 * Function called after failed authentication
	 *
	 * You can use this to do some logging for example
	 *
	 * @since 14.0.0
	 */
	protected function authFailed() {
	}

	/**
	 * Function called after successful authentication
	 *
	 * You can use this to do some logging for example
	 *
	 * @since 14.0.0
	 */
	protected function authSucceeded() {
	}

	/**
	 * Authenticate the share
	 *
	 * @since 14.0.0
	 */
	#[BruteForceProtection(action: 'publicLinkAuth')]
	#[PublicPage]
	#[UseSession]
	final public function authenticate(string $password = '', string $passwordRequest = 'no', string $identityToken = '') {
		// Already authenticated
		if ($this->isAuthenticated()) {
			return $this->getRedirect();
		}

		// Is user requesting a temporary password?
		if ($passwordRequest == '') {
			if ($this->validateIdentity($identityToken)) {
				$this->generatePassword();
				$response = $this->showIdentificationResult(true);
				return $response;
			} else {
				$response = $this->showIdentificationResult(false);
				$response->throttle();
				return $response;
			}
		}

		if (!$this->verifyPassword($password)) {
			$this->authFailed();
			$response = $this->showAuthFailed();
			$response->throttle();
			return $response;
		}

		$this->session->regenerateId(true, true);
		$response = $this->getRedirect();

		$this->session->set('public_link_authenticated_token', $this->getToken());
		$this->session->set('public_link_authenticated_password_hash', $this->getPasswordHash());

		$this->authSucceeded();

		return $response;
	}

	/**
	 * Default landing page
	 *
	 * @since 14.0.0
	 */
	abstract public function showShare(): TemplateResponse;

	/**
	 * @since 14.0.0
	 */
	final public function getAuthenticationRedirect(string $redirect): RedirectResponse {
		return new RedirectResponse(
			$this->urlGenerator->linkToRoute($this->getRoute('showAuthenticate'), ['token' => $this->getToken(), 'redirect' => $redirect])
		);
	}


	/**
	 * @since 14.0.0
	 */
	private function getRoute(string $function): string {
		$app = strtolower($this->appName);
		$class = (new \ReflectionClass($this))->getShortName();
		if (str_ends_with($class, 'Controller')) {
			$class = substr($class, 0, -10);
		}
		return $app . '.' . $class . '.' . $function;
	}

	/**
	 * @since 14.0.0
	 */
	private function getRedirect(): RedirectResponse {
		//Get all the stored redirect parameters:
		$params = $this->session->get('public_link_authenticate_redirect');

		$route = $this->getRoute('showShare');

		if ($params === null) {
			$params = [
				'token' => $this->getToken(),
			];
		} else {
			$params = json_decode($params, true);
			if (isset($params['_route'])) {
				$route = $params['_route'];
				unset($params['_route']);
			}

			// If the token doesn't match the rest of the arguments can't be trusted either
			if (isset($params['token']) && $params['token'] !== $this->getToken()) {
				$params = [
					'token' => $this->getToken(),
				];
			}

			// We need a token
			if (!isset($params['token'])) {
				$params = [
					'token' => $this->getToken(),
				];
			}
		}

		return new RedirectResponse($this->urlGenerator->linkToRoute($route, $params));
	}
}