Skip to content

Commit 8f62112

Browse files
authored
Merge branch 'master' into update-pull-request-template
2 parents f4e0b2a + 802a761 commit 8f62112

File tree

12 files changed

+232
-29
lines changed

12 files changed

+232
-29
lines changed

.gitignore

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,26 @@
22
.DS_Store
33
.coverage
44
.vagrant
5-
/.vscode/
65
/*.egg
76
/*.egg-info
87
/*.eggs
98
/.conda/
109
/.idea/
1110
/.jupyter/
1211
/.local/
13-
/.venv/
1412
/.pipenv-requires
13+
/.venv/
14+
/.vscode/
1515
/build/
1616
/dist/
17+
/docs/docs/changelog.md
18+
/docs/docs/index.md
1719
/node_modules/
1820
/notebooks*/
21+
/rsconnect-build
22+
/rsconnect-build-test
1923
/rsconnect/version.py
20-
htmlcov
2124
/tests/testdata/**/rsconnect-python/
25+
htmlcov
2226
test-home/
23-
/docs/docs/index.md
24-
/docs/docs/changelog.md
25-
/rsconnect-build
26-
/rsconnect-build-test
27+
venv

rsconnect/api.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818

1919
import re
2020
from warnings import warn
21-
from six import text_type
2221
import gc
2322

2423
from . import validation
24+
from .certificates import read_certificate_file
2525
from .http_support import HTTPResponse, HTTPServer, append_to_path, CookieJar
2626
from .log import logger, connect_logger, cls_logged, console_logger
2727
from .models import AppModes
@@ -360,7 +360,7 @@ def __init__(
360360
url: str = None,
361361
api_key: str = None,
362362
insecure: bool = False,
363-
cacert: IO = None,
363+
cacert: str = None,
364364
ca_data: str = None,
365365
cookies=None,
366366
account=None,
@@ -415,7 +415,7 @@ def setup_remote_server(
415415
url: str = None,
416416
api_key: str = None,
417417
insecure: bool = False,
418-
cacert: IO = None,
418+
cacert: str = None,
419419
ca_data: str = None,
420420
account_name: str = None,
421421
token: str = None,
@@ -433,7 +433,7 @@ def setup_remote_server(
433433
)
434434

435435
if cacert and not ca_data:
436-
ca_data = text_type(cacert.read())
436+
ca_data = read_certificate_file(cacert)
437437

438438
server_data = ServerStore().resolve(name, url)
439439
if server_data.from_store:
@@ -507,7 +507,7 @@ def validate_server(
507507
url: str = None,
508508
api_key: str = None,
509509
insecure: bool = False,
510-
cacert: IO = None,
510+
cacert: str = None,
511511
api_key_is_required: bool = False,
512512
account_name: str = None,
513513
token: str = None,
@@ -528,7 +528,7 @@ def validate_connect_server(
528528
url: str = None,
529529
api_key: str = None,
530530
insecure: bool = False,
531-
cacert: IO = None,
531+
cacert: str = None,
532532
api_key_is_required: bool = False,
533533
**kwargs
534534
):
@@ -551,7 +551,7 @@ def validate_connect_server(
551551

552552
ca_data = None
553553
if cacert:
554-
ca_data = text_type(cacert.read())
554+
ca_data = read_certificate_file(cacert)
555555
api_key = api_key or self.remote_server.api_key
556556
insecure = insecure or self.remote_server.insecure
557557
if not ca_data:

rsconnect/certificates.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from pathlib import Path
2+
3+
BINARY_ENCODED_FILETYPES = [".cer", ".der"]
4+
TEXT_ENCODED_FILETYPES = [".ca-bundle", ".crt", ".key", ".pem"]
5+
6+
7+
def read_certificate_file(location: str):
8+
"""Reads a certificate file from disk.
9+
10+
The file type (suffix) is used to determine the file encoding.
11+
Assumption are made based on standard SSL practices.
12+
13+
Files ending in '.cer' and '.der' are assumed DER (Distinguished
14+
Encoding Rules) files encoded in binary format.
15+
16+
Files ending in '.ca-bundle', '.crt', '.key', and '.pem' are PEM
17+
(Privacy Enhanced Mail) files encoded in plain-text format.
18+
"""
19+
20+
path = Path(location)
21+
suffix = path.suffix
22+
23+
if suffix in BINARY_ENCODED_FILETYPES:
24+
with open(path, "rb") as file:
25+
return file.read()
26+
27+
if suffix in TEXT_ENCODED_FILETYPES:
28+
with open(path, "r") as file:
29+
return file.read()
30+
31+
types = BINARY_ENCODED_FILETYPES + TEXT_ENCODED_FILETYPES
32+
types = sorted(types)
33+
types = [f"'{_}'" for _ in types]
34+
human_readable_string = ", ".join(types[:-1]) + ", or " + types[-1]
35+
raise RuntimeError(
36+
f"The certificate file type is not recognized. Expected {human_readable_string}. Found '{suffix}'."
37+
)

rsconnect/main.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
import typing
77
import textwrap
88
import click
9-
from six import text_type
109
from os.path import abspath, dirname, exists, isdir, join
1110
from functools import wraps
11+
12+
from rsconnect.certificates import read_certificate_file
13+
1214
from .environment import EnvironmentException
1315
from .exception import RSConnectException
1416
from .actions import (
@@ -129,7 +131,7 @@ def server_args(func):
129131
"--cacert",
130132
"-c",
131133
envvar="CONNECT_CA_CERTIFICATE",
132-
type=click.File(),
134+
type=click.Path(exists=True, file_okay=True, dir_okay=False),
133135
help="The path to trusted TLS CA certificates.",
134136
)
135137
@click.option("--verbose", "-v", is_flag=True, help="Print detailed messages.")
@@ -269,7 +271,7 @@ def _test_server_and_api(server, api_key, insecure, ca_cert):
269271
:return: a tuple containing an appropriate ConnectServer object and the username
270272
of the user the API key represents (or None, if no key was provided).
271273
"""
272-
ca_data = ca_cert and text_type(ca_cert.read())
274+
ca_data = ca_cert and ca_cert.read()
273275
me = None
274276

275277
with cli_feedback("Checking %s" % server):
@@ -312,7 +314,7 @@ def _test_rstudio_creds(server: api.PositServer):
312314
"--cacert",
313315
"-c",
314316
envvar="CONNECT_CA_CERTIFICATE",
315-
type=click.File(),
317+
type=click.Path(exists=True, file_okay=True, dir_okay=False),
316318
help="The path to trusted TLS CA certificates.",
317319
)
318320
@click.option(
@@ -344,7 +346,10 @@ def bootstrap(
344346
logger.debug("Generated JWT:\n" + bootstrap_token)
345347

346348
logger.debug("Insecure: " + str(insecure))
347-
ca_data = cacert and text_type(cacert.read())
349+
350+
ca_data = None
351+
if cacert:
352+
ca_data = read_certificate_file(cacert)
348353

349354
with cli_feedback("", stderr=True):
350355
connect_server = RSConnectServer(
@@ -398,7 +403,7 @@ def bootstrap(
398403
"--cacert",
399404
"-c",
400405
envvar="CONNECT_CA_CERTIFICATE",
401-
type=click.File(),
406+
type=click.Path(exists=True, file_okay=True, dir_okay=False),
402407
help="The path to trusted TLS CA certificates.",
403408
)
404409
@click.option("--verbose", "-v", is_flag=True, help="Print detailed messages.")
@@ -1674,7 +1679,7 @@ def content():
16741679
"--cacert",
16751680
"-c",
16761681
envvar="CONNECT_CA_CERTIFICATE",
1677-
type=click.File(),
1682+
type=click.Path(exists=True, file_okay=True, dir_okay=False),
16781683
help="The path to trusted TLS CA certificates.",
16791684
)
16801685
@click.option(
@@ -1768,7 +1773,7 @@ def content_search(
17681773
"--cacert",
17691774
"-c",
17701775
envvar="CONNECT_CA_CERTIFICATE",
1771-
type=click.File(),
1776+
type=click.Path(exists=True, file_okay=True, dir_okay=False),
17721777
help="The path to trusted TLS CA certificates.",
17731778
)
17741779
@click.option(
@@ -1819,7 +1824,7 @@ def content_describe(name, server, api_key, insecure, cacert, guid, verbose):
18191824
"--cacert",
18201825
"-c",
18211826
envvar="CONNECT_CA_CERTIFICATE",
1822-
type=click.File(),
1827+
type=click.Path(exists=True, file_okay=True, dir_okay=False),
18231828
help="The path to trusted TLS CA certificates.",
18241829
)
18251830
@click.option(
@@ -1888,7 +1893,7 @@ def build():
18881893
"--cacert",
18891894
"-c",
18901895
envvar="CONNECT_CA_CERTIFICATE",
1891-
type=click.File(),
1896+
type=click.Path(exists=True, file_okay=True, dir_okay=False),
18921897
help="The path to trusted TLS CA certificates.",
18931898
)
18941899
@click.option(
@@ -1942,7 +1947,7 @@ def add_content_build(name, server, api_key, insecure, cacert, guid, verbose):
19421947
"--cacert",
19431948
"-c",
19441949
envvar="CONNECT_CA_CERTIFICATE",
1945-
type=click.File(),
1950+
type=click.Path(exists=True, file_okay=True, dir_okay=False),
19461951
help="The path to trusted TLS CA certificates.",
19471952
)
19481953
@click.option(
@@ -2005,7 +2010,7 @@ def remove_content_build(name, server, api_key, insecure, cacert, guid, all, pur
20052010
"--cacert",
20062011
"-c",
20072012
envvar="CONNECT_CA_CERTIFICATE",
2008-
type=click.File(),
2013+
type=click.Path(exists=True, file_okay=True, dir_okay=False),
20092014
help="The path to trusted TLS CA certificates.",
20102015
)
20112016
@click.option("--status", type=click.Choice(BuildStatus._all), help="Filter results by status of the build operation.")
@@ -2053,7 +2058,7 @@ def list_content_build(name, server, api_key, insecure, cacert, status, guid, ve
20532058
"--cacert",
20542059
"-c",
20552060
envvar="CONNECT_CA_CERTIFICATE",
2056-
type=click.File(),
2061+
type=click.Path(exists=True, file_okay=True, dir_okay=False),
20572062
help="The path to trusted TLS CA certificates.",
20582063
)
20592064
@click.option(
@@ -2104,7 +2109,7 @@ def get_build_history(name, server, api_key, insecure, cacert, guid, verbose):
21042109
"--cacert",
21052110
"-c",
21062111
envvar="CONNECT_CA_CERTIFICATE",
2107-
type=click.File(),
2112+
type=click.Path(exists=True, file_okay=True, dir_okay=False),
21082113
help="The path to trusted TLS CA certificates.",
21092114
)
21102115
@click.option(
@@ -2167,7 +2172,7 @@ def get_build_logs(name, server, api_key, insecure, cacert, guid, task_id, forma
21672172
"--cacert",
21682173
"-c",
21692174
envvar="CONNECT_CA_CERTIFICATE",
2170-
type=click.File(),
2175+
type=click.Path(exists=True, file_okay=True, dir_okay=False),
21712176
help="The path to trusted TLS CA certificates.",
21722177
)
21732178
@click.option(

tests/test_certificates.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from tempfile import NamedTemporaryFile
2+
from unittest import TestCase
3+
4+
from rsconnect.certificates import read_certificate_file
5+
6+
7+
class ParseCertificateFileTestCase(TestCase):
8+
9+
def test_parse_certificate_file_ca_bundle(self):
10+
res = read_certificate_file("tests/testdata/certificates/localhost.ca-bundle")
11+
self.assertTrue(res)
12+
13+
def test_parse_certificate_file_cer(self):
14+
res = read_certificate_file("tests/testdata/certificates/localhost.cer")
15+
self.assertTrue(res)
16+
17+
def test_parse_certificate_file_crt(self):
18+
res = read_certificate_file("tests/testdata/certificates/localhost.crt")
19+
self.assertTrue(res)
20+
21+
def test_parse_certificate_file_der(self):
22+
res = read_certificate_file("tests/testdata/certificates/localhost.der")
23+
self.assertTrue(res)
24+
25+
def test_parse_certificate_file_key(self):
26+
res = read_certificate_file("tests/testdata/certificates/localhost.key")
27+
self.assertTrue(res)
28+
29+
def test_parse_certificate_file_pem(self):
30+
res = read_certificate_file("tests/testdata/certificates/localhost.pem")
31+
self.assertTrue(res)
32+
33+
def test_parse_certificate_file_csr(self):
34+
with self.assertRaises(RuntimeError):
35+
read_certificate_file("tests/testdata/certificates/localhost.csr")
36+
37+
def test_parse_certificate_file_invalid(self):
38+
with NamedTemporaryFile() as tmpfile:
39+
with self.assertRaises(RuntimeError):
40+
read_certificate_file(tmpfile.name)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDETCCAfkCFGouyhXhN5LZGv4+gVbr+IXMNIB+MA0GCSqGSIb3DQEBCwUAMEUx
3+
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
4+
cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjMwMTE4MTkwMTA0WhcNMjQwMTE4MTkw
5+
MTA0WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE
6+
CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC
7+
AQ8AMIIBCgKCAQEAw8rWVc8S3nU86ybTRpjus3+AvSoQF7yf5yvFzcm8lnNptOsA
8+
xP9l4NnnWwdJv87JtUAAdL0MxRrWuhsZFAJh5aCMOGL5mdib80dIy7gqNrg6H2lq
9+
6ciM5yEpcJo29qtEjfExmRYHNQRbd5OjhggzAj2oCLXPQqSRNVfsmNsm/AxkdVXf
10+
tJzOEEulb1yh9FMqn8skWxbFuuua/iVzxX5/r9R8BVdsFNlH69cwV2nI/OAOySHg
11+
zHwWZKVUtQfOkv9jm6CCzpLRrnqarvXt6GmaQFGuqFaAebjPMlp/53csMWQCsrp7
12+
Psy/JXKUJ3Dogk2PvziEgzGg2Nf2f2lKXr3WZQIDAQABMA0GCSqGSIb3DQEBCwUA
13+
A4IBAQB3UsUO5XWlzaO6LsGFCsNbHxH+LxJsejGmnABQt6qzwFF1fs6ixl0RkUsE
14+
6/wKKGZdZw9fDotQeDB7zYfQmOqVJtBh/yrmBsW9qzBIJTp/0RSlNsYueyrnGMi5
15+
+3g+KHLhnOD9tvnPiz8Haoln2XaGM8iZ+HlVUHxHViWqKTDRcBOmtjglFHmsNy+S
16+
UFYqgjVbRZNLWOhkC+7LMQutOzfPbO/5zrCSc4nUUhWEYa4AsmYKKMu10VH/EXtB
17+
QgkX8ZIRa1P8iIe1YDghyjEKBU1WCR7bvbryMTp0HjbSGCuNiiddMesPdwkmPH5/
18+
Y/tWij/h+jS8s6uPtyz4KQUcP7gg
19+
-----END CERTIFICATE-----
20+
-----BEGIN CERTIFICATE-----
21+
MIIDETCCAfkCFAro99muVq+KLo92JIXJVyMtLnmBMA0GCSqGSIb3DQEBCwUAMEUx
22+
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
23+
cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjMwMTE4MTkwMzQwWhcNMjQwMTE4MTkw
24+
MzQwWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE
25+
CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC
26+
AQ8AMIIBCgKCAQEAw8rWVc8S3nU86ybTRpjus3+AvSoQF7yf5yvFzcm8lnNptOsA
27+
xP9l4NnnWwdJv87JtUAAdL0MxRrWuhsZFAJh5aCMOGL5mdib80dIy7gqNrg6H2lq
28+
6ciM5yEpcJo29qtEjfExmRYHNQRbd5OjhggzAj2oCLXPQqSRNVfsmNsm/AxkdVXf
29+
tJzOEEulb1yh9FMqn8skWxbFuuua/iVzxX5/r9R8BVdsFNlH69cwV2nI/OAOySHg
30+
zHwWZKVUtQfOkv9jm6CCzpLRrnqarvXt6GmaQFGuqFaAebjPMlp/53csMWQCsrp7
31+
Psy/JXKUJ3Dogk2PvziEgzGg2Nf2f2lKXr3WZQIDAQABMA0GCSqGSIb3DQEBCwUA
32+
A4IBAQC+Fx+w7kWmvbwrduzYqKW4k8oFFrjolp1x9Hw7cbpx0qoF5sQadOaYMSt2
33+
sBbdZ0qGqlYbRSOYlK9CbXWfzb2Q6ibi6JqkWU+05NYQecj/p1uEwHOxbsvyV2dt
34+
SEGYkJzyRhxS0gI97A4ati7uQ57ptM2LcsVK2Fu+C3DlpU9FrIXnvZj3I+S+WZ7s
35+
sYmqv2/Rf8z+Sy0qhxVwbHslKWuQJpXoUpXwOpZNiXgfV1VnjMTaWaagF0MobsHT
36+
MzE6Dj6f6v2NkfQj7gNQs9ueg4uaACRlUzM/E6Xx1eCL2VXl5nX1ytqW4gKFvWF7
37+
xeuMzMhz0EsvN4hhQKEMWDH7p31q
38+
-----END CERTIFICATE-----
789 Bytes
Binary file not shown.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDETCCAfkCFGouyhXhN5LZGv4+gVbr+IXMNIB+MA0GCSqGSIb3DQEBCwUAMEUx
3+
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
4+
cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjMwMTE4MTkwMTA0WhcNMjQwMTE4MTkw
5+
MTA0WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE
6+
CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC
7+
AQ8AMIIBCgKCAQEAw8rWVc8S3nU86ybTRpjus3+AvSoQF7yf5yvFzcm8lnNptOsA
8+
xP9l4NnnWwdJv87JtUAAdL0MxRrWuhsZFAJh5aCMOGL5mdib80dIy7gqNrg6H2lq
9+
6ciM5yEpcJo29qtEjfExmRYHNQRbd5OjhggzAj2oCLXPQqSRNVfsmNsm/AxkdVXf
10+
tJzOEEulb1yh9FMqn8skWxbFuuua/iVzxX5/r9R8BVdsFNlH69cwV2nI/OAOySHg
11+
zHwWZKVUtQfOkv9jm6CCzpLRrnqarvXt6GmaQFGuqFaAebjPMlp/53csMWQCsrp7
12+
Psy/JXKUJ3Dogk2PvziEgzGg2Nf2f2lKXr3WZQIDAQABMA0GCSqGSIb3DQEBCwUA
13+
A4IBAQB3UsUO5XWlzaO6LsGFCsNbHxH+LxJsejGmnABQt6qzwFF1fs6ixl0RkUsE
14+
6/wKKGZdZw9fDotQeDB7zYfQmOqVJtBh/yrmBsW9qzBIJTp/0RSlNsYueyrnGMi5
15+
+3g+KHLhnOD9tvnPiz8Haoln2XaGM8iZ+HlVUHxHViWqKTDRcBOmtjglFHmsNy+S
16+
UFYqgjVbRZNLWOhkC+7LMQutOzfPbO/5zrCSc4nUUhWEYa4AsmYKKMu10VH/EXtB
17+
QgkX8ZIRa1P8iIe1YDghyjEKBU1WCR7bvbryMTp0HjbSGCuNiiddMesPdwkmPH5/
18+
Y/tWij/h+jS8s6uPtyz4KQUcP7gg
19+
-----END CERTIFICATE-----
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-----BEGIN CERTIFICATE REQUEST-----
2+
MIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
3+
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
4+
AQEBBQADggEPADCCAQoCggEBAMPK1lXPEt51POsm00aY7rN/gL0qEBe8n+crxc3J
5+
vJZzabTrAMT/ZeDZ51sHSb/OybVAAHS9DMUa1robGRQCYeWgjDhi+ZnYm/NHSMu4
6+
Kja4Oh9paunIjOchKXCaNvarRI3xMZkWBzUEW3eTo4YIMwI9qAi1z0KkkTVX7Jjb
7+
JvwMZHVV37SczhBLpW9cofRTKp/LJFsWxbrrmv4lc8V+f6/UfAVXbBTZR+vXMFdp
8+
yPzgDskh4Mx8FmSlVLUHzpL/Y5uggs6S0a56mq717ehpmkBRrqhWgHm4zzJaf+d3
9+
LDFkArK6ez7MvyVylCdw6IJNj784hIMxoNjX9n9pSl691mUCAwEAAaAAMA0GCSqG
10+
SIb3DQEBCwUAA4IBAQCbHHGn119/PjC7gd18PEGYGss/7rSylpo8FlHT28SI/YjJ
11+
HTO2ebD0jU4nHcJo8ihOTBUaGRhzURXMX81ernRnFMTy87XKA7FDt5HXLX3i5EYC
12+
89S3S8ncaDHC99D1vG4hGAAFuUYi8JWipKaCBKgoHm6VsYqCQeVOPll7PSC3Szyc
13+
aWR/emEcz/tHXG5a6it6PsWo3jKDPeSPDUtlzZcUz4ssbQih8uEdoTBKwk4mnaCc
14+
XVndRRfPnhHMOr9BdCjd+LYU0PRtJpsv7pqbHPTQEPEYh7GD99d5jDKtff6J4YkM
15+
ljLCS+SOPMMTJTEaGuR7NvuIcoj4vODOSEkSUxv9
16+
-----END CERTIFICATE REQUEST-----
789 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)