# HG changeset patch # User Sebastien Jodogne # Date 1787322767 -7200 # Node ID ff7d48d8d51a31ac89a45a61611037903afce4c1 # Parent 10bc736775b1a862917281cd8e3d335f209f1c86 added Resources/Samples/SampleHttpAuthentication.py diff -r 10bc736775b1 -r ff7d48d8d51a NEWS --- 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) diff -r 10bc736775b1 -r ff7d48d8d51a Resources/Samples/SampleHttpAuthentication.py --- /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 . +## + + +# +# 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 = ''' + + + + +Orthanc sample authentication + + +

Orthanc sample authentication

+%s +
+

Username:

+

Password:

+ +
+ +''' + + +HTML_PAGE_LOGGED = ''' + + + + +Orthanc sample authentication + + +

Orthanc sample authentication

+

You are logged as: %s

+
+ +
+

+ + +''' + +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 = '

Bad credentials were provided

' + 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)