Mercurial > hg > orthanc-python
changeset 334:ff7d48d8d51a default tip
added Resources/Samples/SampleHttpAuthentication.py
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Fri, 21 Aug 2026 16:32:47 +0200 |
| parents | 10bc736775b1 |
| children | |
| files | NEWS Resources/Samples/SampleHttpAuthentication.py |
| diffstat | 2 files changed, 180 insertions(+), 3 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Fri Aug 21 15:14:47 2026 +0200 +++ b/NEWS Fri Aug 21 16:32:47 2026 +0200 @@ -4,11 +4,13 @@ => Maximum SDK version: 1.13.0 (default) <= => Minimum SDK version: 1.7.2 <= -* Wrapped OrthancPluginRegisterHttpAuthentication() as orthanc.RegisterHttpAuthentication() +* Wrapped "OrthancPluginRegisterHttpAuthentication()" as + "orthanc.RegisterHttpAuthentication()", for which a sample is + available in "./Resources/Samples/SampleHttpAuthentication.py" * Give access to the authentication payload in the REST callbacks, which requires Orthanc SDK 1.12.9 -* Wrapped DicomInsante.GetInstanceRemoteIp() and DicomInsante.GetInstanceCalledAet(), - which requires Orthanc SDK 1.13.1 (not released yet) +* Wrapped "DicomInstance.GetInstanceRemoteIp()" and "DicomInstance.GetInstanceCalledAet()", + which require Orthanc SDK 1.13.1 (not released yet) Version 7.1 (2026-04-07)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/SampleHttpAuthentication.py Fri Aug 21 16:32:47 2026 +0200 @@ -0,0 +1,175 @@ +# SPDX-FileCopyrightText: 2020-2023 Osimis S.A., 2024-2026 Orthanc Team SRL, 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain +# SPDX-License-Identifier: AGPL-3.0-or-later + +## +## Python plugin for Orthanc +## Copyright (C) 2020-2023 Osimis S.A., Belgium +## Copyright (C) 2024-2026 Orthanc Team SRL, Belgium +## Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium +## +## This program is free software: you can redistribute it and/or +## modify it under the terms of the GNU Affero General Public License +## as published by the Free Software Foundation, either version 3 of +## the License, or (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## Affero General Public License for more details. +## +## You should have received a copy of the GNU Affero General Public License +## along with this program. If not, see <http://www.gnu.org/licenses/>. +## + + +# +# Sample Python plugin illustrating how to implement basic +# cookie-based user authentication. +# +# This sample uses plain session cookies. A more realistic +# implementation would use JWT-based authentication. +# + + +import json +import orthanc +import urllib.parse + + +CREDENTIALS = { + 'admin' : 'admin', +} + + +ROOT = '/authentication-sample' +COOKIE_LOGGED_USER = 'logged_user' +COOKIE_BAD_CREDENTIALS = 'bad_credentials' + + +HTML_PAGE_LOGIN = ''' +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<title>Orthanc sample authentication</title> +</head> +<body> +<h1>Orthanc sample authentication</h1> +%s +<form action="do-login" method="POST"> +<p>Username: <input type="text" name="username"></input></p> +<p>Password: <input type="password" name="password"></input></p> +<button type="submit">Log in</button> +</form> +</body> +</html>''' + + +HTML_PAGE_LOGGED = ''' +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<title>Orthanc sample authentication</title> +</head> +<body> +<h1>Orthanc sample authentication</h1> +<p>You are logged as: <b>%s</b></p> +<form action="do-logout" method="POST"> +<button type="submit">Log out</button> +</form> +<p><a href=".." target="_blank"><button>Open Orthanc Explorer</button></a></p> +</body> +</html> +''' + +def SetSessionCookie(output, cookie, value): + output.SetHttpHeader('Set-Cookie', '%s=%s; HttpOnly; SameSite=Lax; Secure; Path=/' % (cookie, value)) + + +def ClearSessionCookie(output, cookie): + output.SetHttpHeader('Set-Cookie', '%s=; HttpOnly; SameSite=Lax; Secure; Path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT' % cookie) + + +def Login(output, uri, **request): + authentication = json.loads(request['authentication_payload']) + + if 'username' in authentication: + output.AnswerBuffer(HTML_PAGE_LOGGED % authentication['username'], 'text/html') + else: + if authentication.get('bad_credentials') == 'true': + bad_credentials = '<p><b>Bad credentials were provided</b></p>' + ClearSessionCookie(output, COOKIE_BAD_CREDENTIALS) # Only warn once about bad credentials + else: + bad_credentials = '' + + output.AnswerBuffer(HTML_PAGE_LOGIN % bad_credentials, 'text/html') + + +def DoLogin(output, uri, **request): + body = dict(urllib.parse.parse_qsl(request['body'].decode('utf-8'))) + username = body.get('username') + password = body.get('password') + + if (username != None and + password != None and + CREDENTIALS.get(username) == password): + # Correct credentials + SetSessionCookie(output, COOKIE_LOGGED_USER, username) + ClearSessionCookie(output, COOKIE_BAD_CREDENTIALS) + else: + # Wrong credentials + ClearSessionCookie(output, COOKIE_LOGGED_USER) + SetSessionCookie(output, COOKIE_BAD_CREDENTIALS, 'true') + + output.SetHttpHeader('Location', 'index.html') + output.SendHttpStatusCode(302) # Redirection + + +def DoLogout(output, uri, **request): + ClearSessionCookie(output, COOKIE_LOGGED_USER) + ClearSessionCookie(output, COOKIE_BAD_CREDENTIALS) + output.SetHttpHeader('Location', 'index.html') + output.SendHttpStatusCode(302) # Redirection + + +def DoAuthentication(uri, ip, headers, get): + authentication_payload = { + # It is a good practice to inform other plugins (such as + # orthanc-wsi) about the plugin that generated the + # authentication payload + 'source' : 'orthanc-python-sample-authentication', + } + is_logged = False + + for (key, value) in headers.items(): + if key == 'cookie': + for value in value.split(';'): + cookie = value.split('=') + cookieName = cookie[0].strip() + cookieValue = cookie[1].strip() + if cookieName == COOKIE_LOGGED_USER: + authentication_payload['username'] = cookieValue + is_logged = True + elif cookieName == COOKIE_BAD_CREDENTIALS: + authentication_payload['bad_credentials'] = cookieValue + + authentication_payload = json.dumps(authentication_payload).encode('utf-8') # From JSON string to bytes + + if uri.startswith(ROOT): + # Always grant access to the routes related to login/logout + return (orthanc.HttpAuthenticationStatus.GRANTED, authentication_payload, None) + elif is_logged: + # You could return orthanc.HttpAuthenticationStatus.FORBIDDEN + # if the logged user tries to access a resource for which the + # credentials are not sufficient + return (orthanc.HttpAuthenticationStatus.GRANTED, authentication_payload, None) + else: + # Request authentication is the user is not logged in yet + return (orthanc.HttpAuthenticationStatus.REDIRECT, authentication_payload, '%s/index.html' % ROOT) + + +orthanc.RegisterHttpAuthenticationCallback(DoAuthentication) +orthanc.RegisterRestCallback('%s/do-login' % ROOT, DoLogin) +orthanc.RegisterRestCallback('%s/do-logout' % ROOT, DoLogout) +orthanc.RegisterRestCallback('%s/index.html' % ROOT, Login)
