Skip to content

Commit 6d87fc7

Browse files
authored
Merge pull request #3289 from esdc-esac-esa-int/ESA_gaia_GAIAPCR-1341_new_tap_session_key
GAIA: update the authentication implementation to read the cookies sent by the new ESAC tap mechanism
2 parents ae9dd53 + 04df663 commit 6d87fc7

File tree

8 files changed

+176
-37
lines changed

8 files changed

+176
-37
lines changed

CHANGES.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ heasarc
2727
no data associated with that row rather than filtering it out. [#3275]
2828

2929

30+
utils.tap
31+
^^^^^^^^^
32+
33+
- Get the cookie associated to the keys JSESSIONID or SESSION due to the tap library release at ESAC. [#3289]
34+
35+
3036
Infrastructure, Utility and Other Changes and Additions
3137
-------------------------------------------------------
3238

astroquery/utils/tap/conn/tapconn.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717

1818
import http.client as httplib
1919
import mimetypes
20+
import os
2021
import platform
22+
import requests
2123
import time
22-
import os
23-
from astroquery.utils.tap.xmlparser import utils
24-
from astroquery.utils.tap import taputils
25-
from astroquery import version
2624

27-
import requests
25+
from astroquery import version
26+
from astroquery.utils.tap import taputils
27+
from astroquery.utils.tap.xmlparser import utils
2828

2929
__all__ = ['TapConn']
3030

@@ -485,6 +485,22 @@ def find_header(self, headers, key):
485485
"""
486486
return taputils.taputil_find_header(headers, key)
487487

488+
def find_all_headers(self, headers, key):
489+
"""Searches for the specified keyword
490+
491+
Parameters
492+
----------
493+
headers : HTTP(s) headers object, mandatory
494+
HTTP(s) response headers
495+
key : str, mandatory
496+
header key to be searched for
497+
498+
Returns
499+
-------
500+
A list of requested header values or an emtpy list if no header is found
501+
"""
502+
return taputils.taputil_find_all_headers(headers, key)
503+
488504
def dump_to_file(self, output, response):
489505
"""Writes the connection response into the specified output
490506
@@ -585,7 +601,7 @@ def get_file_from_header(self, headers):
585601
if content_disposition is not None:
586602
p = content_disposition.find('filename="')
587603
if p >= 0:
588-
filename = os.path.basename(content_disposition[p+10:len(content_disposition)-1])
604+
filename = os.path.basename(content_disposition[p + 10:len(content_disposition) - 1])
589605
content_encoding = self.find_header(headers, 'Content-Encoding')
590606

591607
if content_encoding is not None:
@@ -722,7 +738,7 @@ def encode_multipart(self, fields, files):
722738

723739
def __str__(self):
724740
return f"\tHost: {self.__connHost}\n\tUse HTTPS: {self.__isHttps}" \
725-
f"\n\tPort: {self.__connPort}\n\tSSL Port: {self.__connPortSsl}"
741+
f"\n\tPort: {self.__connPort}\n\tSSL Port: {self.__connPortSsl}"
726742

727743

728744
class ConnectionHandler:

astroquery/utils/tap/conn/tests/DummyConnHandler.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ def get_file_from_header(self, headers):
138138
def find_header(self, headers, key):
139139
return taputils.taputil_find_header(headers, key)
140140

141+
def find_all_headers(self, headers, key):
142+
return taputils.taputil_find_all_headers(headers, key)
143+
141144
def execute_table_edit(self, data,
142145
content_type="application/x-www-form-urlencoded",
143146
verbose=False):

astroquery/utils/tap/conn/tests/test_conn.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,59 @@ def test_login():
115115
assert r.get_method() == 'POST'
116116
assert r.get_context() == context
117117
assert r.get_body() == data
118+
119+
120+
def test_find_header():
121+
host = "testHost"
122+
tap = TapConn(ishttps=False, host=host)
123+
124+
headers = [('Date', 'Sat, 12 Apr 2025 05:10:47 GMT'),
125+
('Server', 'Apache/2.4.6 (Red Hat Enterprise Linux) OpenSSL/1.0.2k-fips mod_jk/1.2.43'),
126+
('Set-Cookie', 'JSESSIONID=E677B51BA5C4837347D1E17D4E36647E; Path=/data-server; Secure; HttpOnly'),
127+
('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'),
128+
('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate'), ('Pragma', 'no-cache'),
129+
('Expires', '0'), ('X-Frame-Options', 'SAMEORIGIN'),
130+
('Set-Cookie', 'SESSION=ZjQ3MjIzMDAt; Path=/data-server; Secure; HttpOnly; SameSite=Lax'),
131+
('Transfer-Encoding', 'chunked'), ('Content-Type', 'text/plain; charset=UTF-8')]
132+
key = 'Set-Cookie'
133+
result = tap.find_header(headers, key)
134+
135+
assert (result == "JSESSIONID=E677B51BA5C4837347D1E17D4E36647E; Path=/data-server; Secure; HttpOnly")
136+
137+
138+
def test_find_all_headers():
139+
host = "testHost"
140+
tap = TapConn(ishttps=False, host=host)
141+
142+
headers = [('Date', 'Sat, 12 Apr 2025 05:10:47 GMT'),
143+
('Server', 'Apache/2.4.6 (Red Hat Enterprise Linux) OpenSSL/1.0.2k-fips mod_jk/1.2.43'),
144+
('Set-Cookie', 'JSESSIONID=E677B51BA5C4837347D1E17D4E36647E; Path=/data-server; Secure; HttpOnly'),
145+
('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'),
146+
('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate'), ('Pragma', 'no-cache'),
147+
('Expires', '0'), ('X-Frame-Options', 'SAMEORIGIN'),
148+
('Set-Cookie', 'SESSION=ZjQ3MjIzMDAtNjNiYy00Mj; Path=/data-server; Secure; HttpOnly; SameSite=Lax'),
149+
('Transfer-Encoding', 'chunked'), ('Content-Type', 'text/plain; charset=UTF-8')]
150+
key = 'Set-Cookie'
151+
result = tap.find_all_headers(headers, key)
152+
153+
assert (result[0] == "JSESSIONID=E677B51BA5C4837347D1E17D4E36647E; Path=/data-server; Secure; HttpOnly")
154+
assert (result[1] == "SESSION=ZjQ3MjIzMDAtNjNiYy00Mj; Path=/data-server; Secure; HttpOnly; SameSite=Lax")
155+
156+
157+
def test_get_file_from_header():
158+
host = "testHost"
159+
tap = TapConn(ishttps=False, host=host)
160+
161+
headers = [('Date', 'Sat, 12 Apr 2025 05:10:47 GMT'),
162+
('Server', 'Apache/2.4.6 (Red Hat Enterprise Linux) OpenSSL/1.0.2k-fips mod_jk/1.2.43'),
163+
('Set-Cookie', 'JSESSIONID=E677B51BA5C4837347D1E17D4E36647E; Path=/data-server; Secure; HttpOnly'),
164+
('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'),
165+
('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate'), ('Pragma', 'no-cache'),
166+
('Expires', '0'), ('X-Frame-Options', 'SAMEORIGIN'),
167+
('Set-Cookie', 'SESSION=ZjQ3MjIzMDAtNjNiYy00Mj; Path=/data-server; Secure; HttpOnly; SameSite=Lax'),
168+
('Transfer-Encoding', 'chunked'), ('Content-Type', 'text/plain; charset=UTF-8'),
169+
('Content-Disposition', 'filename="my_file.vot.gz"'), ('Content-Encoding', "gzip")]
170+
171+
result = tap.get_file_from_header(headers)
172+
173+
assert (result == "my_file.vot.gz")

astroquery/utils/tap/core.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@
1515
"""
1616
import getpass
1717
import os
18-
import tempfile
19-
from urllib.parse import urlencode
20-
2118
import requests
19+
import tempfile
2220
from astropy.table.table import Table
21+
from urllib.parse import urlencode
2322

2423
from astroquery import log
2524
from astroquery.utils.tap import taputils
@@ -661,16 +660,23 @@ def __extract_sync_subcontext(self, location):
661660
return location[pos:]
662661

663662
def __findCookieInHeader(self, headers, *, verbose=False):
664-
cookies = self.__connHandler.find_header(headers, 'Set-Cookie')
663+
cookies = self.__connHandler.find_all_headers(headers, 'Set-Cookie')
665664
if verbose:
666665
print(cookies)
667-
if cookies is None:
666+
if not cookies:
668667
return None
669668
else:
670-
items = cookies.split(';')
671-
for i in items:
672-
if i.startswith("JSESSIONID="):
673-
return i
669+
for cook in cookies:
670+
items = cook.split(';')
671+
for item in items:
672+
if item.startswith("SESSION="):
673+
return item
674+
675+
for cook in cookies:
676+
items = cook.split(';')
677+
for item in items:
678+
if item.startswith("JSESSIONID="):
679+
return item
674680
return None
675681

676682
def __parseUrl(self, url, *, verbose=False):

astroquery/utils/tap/taputils.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,28 @@ def taputil_find_header(headers, key):
4949
return None
5050

5151

52+
def taputil_find_all_headers(headers, key):
53+
"""Searches for the specified keyword
54+
55+
Parameters
56+
----------
57+
headers : HTTP(s) headers object, mandatory
58+
HTTP(s) response headers
59+
key : str, mandatory
60+
header key to be searched for
61+
62+
Returns
63+
-------
64+
A list of requested header values or an empty list if not header is found
65+
"""
66+
67+
result = list()
68+
for entry in headers:
69+
if key.lower() == entry[0].lower():
70+
result.append(entry[1])
71+
return result
72+
73+
5274
def taputil_create_sorted_dict_key(dictionaryObject):
5375
"""Searches for the specified keyword
5476

astroquery/utils/tap/tests/test_tap.py

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,21 @@
1212
1313
Created on 30 jun. 2016
1414
"""
15+
import gzip
1516
from pathlib import Path
1617
from unittest.mock import patch
1718
from urllib.parse import quote_plus, urlencode
1819

19-
import gzip
2020
import numpy as np
2121
import pytest
22+
from astropy.table import Table
2223
from requests import HTTPError
2324

24-
from astroquery.utils.tap.model.tapcolumn import TapColumn
25-
25+
from astroquery.utils.tap import taputils
2626
from astroquery.utils.tap.conn.tests.DummyConnHandler import DummyConnHandler
2727
from astroquery.utils.tap.conn.tests.DummyResponse import DummyResponse
2828
from astroquery.utils.tap.core import TapPlus
29-
from astroquery.utils.tap import taputils
30-
from astropy.table import Table
29+
from astroquery.utils.tap.model.tapcolumn import TapColumn
3130

3231

3332
def read_file(filename):
@@ -115,8 +114,8 @@ def test_load_tables_parameters():
115114

116115

117116
def test_load_table():
118-
connHandler = DummyConnHandler()
119-
tap = TapPlus(url="http://test:1111/tap", connhandler=connHandler)
117+
conn_handler = DummyConnHandler()
118+
tap = TapPlus(url="http://test:1111/tap", connhandler=conn_handler)
120119

121120
# No arguments
122121
with pytest.raises(Exception):
@@ -128,7 +127,7 @@ def test_load_table():
128127
tableName = "table1"
129128
fullQualifiedTableName = f"{tableSchema}.{tableName}"
130129
tableRequest = f"tables?tables={fullQualifiedTableName}"
131-
connHandler.set_response(tableRequest, responseLoadTable)
130+
conn_handler.set_response(tableRequest, responseLoadTable)
132131

133132
with pytest.raises(Exception):
134133
tap.load_table(fullQualifiedTableName)
@@ -871,26 +870,26 @@ def test_rename_table():
871870

872871

873872
def __find_table(schemaName, tableName, tables):
874-
qualifiedName = f"{schemaName}.{tableName}"
875-
for table in (tables):
876-
if table.get_qualified_name() == qualifiedName:
873+
qualified_name = f"{schemaName}.{tableName}"
874+
for table in tables:
875+
if table.get_qualified_name() == qualified_name:
877876
return table
878877
# not found: raise exception
879-
pytest.fail(f"Table '{qualifiedName}' not found")
878+
pytest.fail(f"Table '{qualified_name}' not found")
880879

881880

882-
def __find_column(columnName, columns):
883-
for c in (columns):
884-
if c.name == columnName:
881+
def __find_column(column_name, columns):
882+
for c in columns:
883+
if c.name == column_name:
885884
return c
886885
# not found: raise exception
887-
pytest.fail(f"Column '{columnName}' not found")
886+
pytest.fail(f"Column '{column_name}' not found")
888887

889888

890-
def __check_column(column, description, unit, dataType, flag):
889+
def __check_column(column, description, unit, data_type, flag):
891890
assert column.description == description
892891
assert column.unit == unit
893-
assert column.data_type == dataType
892+
assert column.data_type == data_type
894893
assert column.flag == flag
895894

896895

@@ -906,11 +905,11 @@ def __check_results_column(results, columnName, description, unit,
906905
def test_login(mock_login):
907906
conn_handler = DummyConnHandler()
908907
tap = TapPlus(url="http://test:1111/tap", connhandler=conn_handler)
909-
tap.login("user", "password")
908+
tap.login(user="user", password="password")
910909
assert (mock_login.call_count == 1)
911910
mock_login.side_effect = HTTPError("Login error")
912911
with pytest.raises(HTTPError):
913-
tap.login("user", "password")
912+
tap.login(user="user", password="password")
914913
assert (mock_login.call_count == 2)
915914

916915

@@ -923,7 +922,7 @@ def test_login_gui(mock_login_gui, mock_login):
923922
assert (mock_login_gui.call_count == 0)
924923
mock_login_gui.side_effect = HTTPError("Login error")
925924
with pytest.raises(HTTPError):
926-
tap.login("user", "password")
925+
tap.login(user="user", password="password")
927926
assert (mock_login.call_count == 1)
928927

929928

@@ -951,3 +950,33 @@ def test_upload_table():
951950
tap.upload_table(upload_resource=table, table_name=table_name)
952951

953952
assert str(exc_info.value) == f"Table name is not allowed to contain a dot: {table_name}"
953+
954+
955+
def test___findCookieInHeader():
956+
conn_handler = DummyConnHandler()
957+
tap = TapPlus(url="http://test:1111/tap", connhandler=conn_handler)
958+
959+
headers = [('Date', 'Sat, 12 Apr 2025 05:10:47 GMT'),
960+
('Server', 'Apache/2.4.6 (Red Hat Enterprise Linux) OpenSSL/1.0.2k-fips mod_jk/1.2.43'),
961+
('Set-Cookie', 'JSESSIONID=E677B51BA5C4837347D1E17D4E36647E; Path=/data-server; Secure; HttpOnly'),
962+
('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'),
963+
('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate'), ('Pragma', 'no-cache'),
964+
('Expires', '0'), ('X-Frame-Options', 'SAMEORIGIN'),
965+
('Set-Cookie', 'SESSION=ZjQ3MjIzMDAtNjNiYy00Mj; Path=/data-server; Secure; HttpOnly; SameSite=Lax'),
966+
('Transfer-Encoding', 'chunked'), ('Content-Type', 'text/plain; charset=UTF-8')]
967+
968+
result = tap._Tap__findCookieInHeader(headers)
969+
970+
assert (result == "SESSION=ZjQ3MjIzMDAtNjNiYy00Mj")
971+
972+
headers = [('Date', 'Sat, 12 Apr 2025 05:10:47 GMT'),
973+
('Server', 'Apache/2.4.6 (Red Hat Enterprise Linux) OpenSSL/1.0.2k-fips mod_jk/1.2.43'),
974+
('Set-Cookie', 'JSESSIONID=E677B51BA5C4837347D1E17D4E36647E; Path=/data-server; Secure; HttpOnly'),
975+
('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'),
976+
('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate'), ('Pragma', 'no-cache'),
977+
('Expires', '0'), ('X-Frame-Options', 'SAMEORIGIN'),
978+
('Transfer-Encoding', 'chunked'), ('Content-Type', 'text/plain; charset=UTF-8')]
979+
980+
result = tap._Tap__findCookieInHeader(headers)
981+
982+
assert (result == "JSESSIONID=E677B51BA5C4837347D1E17D4E36647E")

docs/gaia/gaia.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,7 @@ The following example shows how to retrieve the DataLink products associated wit
867867
... data_release=data_release, retrieval_type=retrieval_type, data_structure=data_structure)
868868

869869
The DataLink products are stored inside a Python Dictionary. Each of its elements (keys) contains a one-element list that can be extracted as follows:
870+
870871
.. code-block:: python
871872
872873
>>> dl_keys = [inp for inp in datalink.keys()]

0 commit comments

Comments
 (0)