diff --git a/examples/test_examples.py b/examples/test_examples.py index 8d88f1930..70d261637 100644 --- a/examples/test_examples.py +++ b/examples/test_examples.py @@ -19,18 +19,26 @@ # limitations under the License. +from unittest import skip + +from neo4j.v1 import TRUST_ON_FIRST_USE, TRUST_SIGNED_CERTIFICATES from test.util import ServerTestCase +# Do not change the contents of this tagged section without good reason* # tag::minimal-example-import[] from neo4j.v1 import GraphDatabase, basic_auth # end::minimal-example-import[] +# (* "good reason" is defined as knowing what you are doing) + + +auth_token = basic_auth("neo4j", "password") class FreshDatabaseTestCase(ServerTestCase): def setUp(self): ServerTestCase.setUp(self) - session = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password")).session() + session = GraphDatabase.driver("bolt://localhost", auth=auth_token).session() session.run("MATCH (n) DETACH DELETE n") session.close() @@ -42,11 +50,11 @@ def test_minimal_working_example(self): driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password")) session = driver.session() - session.run("CREATE (neo:Person {name:'Neo', age:23})") + session.run("CREATE (a:Person {name:'Arthur', title:'King'})", ) - result = session.run("MATCH (p:Person) WHERE p.name = 'Neo' RETURN p.age") + result = session.run("MATCH (a:Person) WHERE a.name = 'Arthur' RETURN a.name AS name, a.title AS title") while result.next(): - print("Neo is %d years old." % result["p.age"]) + print("%s %s" % (result["title"], result["name"])) session.close() # end::minimal-example[] @@ -68,40 +76,40 @@ def test_configuration(self): def test_tls_require_encryption(self): # tag::tls-require-encryption[] - # TODO: Unfortunately, this feature is not yet implemented for Python - pass + driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password"), encrypted=True) # end::tls-require-encryption[] def test_tls_trust_on_first_use(self): # tag::tls-trust-on-first-use[] - # TODO: Unfortunately, this feature is not yet implemented for Python - pass + driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password"), encrypted=True, trust=TRUST_ON_FIRST_USE) # end::tls-trust-on-first-use[] + assert driver + @skip("testing verified certificates not yet supported ") def test_tls_signed(self): # tag::tls-signed[] - # TODO: Unfortunately, this feature is not yet implemented for Python - pass + driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password"), encrypted=True, trust=TRUST_SIGNED_CERTIFICATES) # end::tls-signed[] + assert driver def test_statement(self): - driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password")) + driver = GraphDatabase.driver("bolt://localhost", auth=auth_token) session = driver.session() # tag::statement[] - session.run("CREATE (person:Person {name: {name}})", {"name": "Neo"}).close() + session.run("CREATE (person:Person {name: {name}})", {"name": "Arthur"}).close() # end::statement[] session.close() def test_statement_without_parameters(self): - driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password")) + driver = GraphDatabase.driver("bolt://localhost", auth=auth_token) session = driver.session() # tag::statement-without-parameters[] - session.run("CREATE (person:Person {name: 'Neo'})").close() + session.run("CREATE (person:Person {name: 'Arthur'})").close() # end::statement-without-parameters[] session.close() def test_result_cursor(self): - driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password")) + driver = GraphDatabase.driver("bolt://localhost", auth=auth_token) session = driver.session() # tag::result-cursor[] search_term = "hammer" @@ -114,67 +122,67 @@ def test_result_cursor(self): session.close() def test_cursor_nesting(self): - driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password")) + driver = GraphDatabase.driver("bolt://localhost", auth=auth_token) session = driver.session() # tag::retain-result-query[] - result = session.run("MATCH (person:Person) WHERE person.dept = {dept} " - "RETURN id(person) AS minion", {"dept": "IT"}) + result = session.run("MATCH (knight:Person:Knight) WHERE knight.castle = {castle} " + "RETURN id(knight) AS knight_id", {"castle": "Camelot"}) while result.next(): - session.run("MATCH (person) WHERE id(person) = {id} " - "MATCH (boss:Person) WHERE boss.name = {boss} " - "CREATE (person)-[:REPORTS_TO]->(boss)", {"id": result["minion"], "boss": "Bob"}) + session.run("MATCH (knight) WHERE id(knight) = {id} " + "MATCH (king:Person) WHERE king.name = {king} " + "CREATE (knight)-[:DEFENDS]->(king)", {"id": result["knight_id"], "king": "Arthur"}) # end::retain-result-query[] session.close() def test_result_retention(self): - driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password")) + driver = GraphDatabase.driver("bolt://localhost", auth=auth_token) session = driver.session() # tag::retain-result-process[] - result = session.run("MATCH (person:Person) WHERE person.dept = {dept} " - "RETURN id(person) AS minion", {"dept": "IT"}) - minion_records = list(result.stream()) - - for record in minion_records: - session.run("MATCH (person) WHERE id(person) = {id} " - "MATCH (boss:Person) WHERE boss.name = {boss} " - "CREATE (person)-[:REPORTS_TO]->(boss)", {"id": record["minion"], "boss": "Bob"}) + result = session.run("MATCH (knight:Person:Knight) WHERE knight.castle = {castle} " + "RETURN id(knight) AS knight_id", {"castle": "Camelot"}) + id_records = list(result.stream()) + + for record in id_records: + session.run("MATCH (knight) WHERE id(knight) = {id} " + "MATCH (king:Person) WHERE king.name = {king} " + "CREATE (knight)-[:DEFENDS]->(king)", {"id": record["knight_id"], "king": "Arthur"}) # end::retain-result-process[] session.close() def test_transaction_commit(self): - driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password")) + driver = GraphDatabase.driver("bolt://localhost", auth=auth_token) session = driver.session() # tag::transaction-commit[] tx = session.begin_transaction() - tx.run("CREATE (p:Person {name: 'The One'})") + tx.run("CREATE (:Person {name: 'Guinevere'})") tx.commit() # end::transaction-commit[] - result = session.run("MATCH (p:Person {name: 'The One'}) RETURN count(p)") + result = session.run("MATCH (p:Person {name: 'Guinevere'}) RETURN count(p)") assert result.next() assert result["count(p)"] == 1 assert result.at_end session.close() def test_transaction_rollback(self): - driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password")) + driver = GraphDatabase.driver("bolt://localhost", auth=auth_token) session = driver.session() # tag::transaction-rollback[] tx = session.begin_transaction() - tx.run("CREATE (p:Person {name: 'The One'})") + tx.run("CREATE (:Person {name: 'Merlin'})") tx.rollback() # end::transaction-rollback[] - result = session.run("MATCH (p:Person {name: 'The One'}) RETURN count(p)") + result = session.run("MATCH (p:Person {name: 'Merlin'}) RETURN count(p)") assert result.next() assert result["count(p)"] == 0 assert result.at_end session.close() def test_result_summary_query_profile(self): - driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password")) + driver = GraphDatabase.driver("bolt://localhost", auth=auth_token) session = driver.session() # tag::result-summary-query-profile[] result = session.run("PROFILE MATCH (p:Person {name: {name}}) " - "RETURN id(p)", {"name": "The One"}) + "RETURN id(p)", {"name": "Arthur"}) while result.next(): pass # skip the records to get to the summary print(result.summary.statement_type) @@ -183,10 +191,10 @@ def test_result_summary_query_profile(self): session.close() def test_result_summary_notifications(self): - driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password")) + driver = GraphDatabase.driver("bolt://localhost", auth=auth_token) session = driver.session() # tag::result-summary-notifications[] - result = session.run("EXPLAIN MATCH (a), (b) RETURN a,b") + result = session.run("EXPLAIN MATCH (king), (queen) RETURN king, queen") while result.next(): pass # skip the records to get to the summary for notification in result.summary.notifications: diff --git a/neo4j/v1/compat.py b/neo4j/v1/compat.py index dc21adad6..7bae32b6d 100644 --- a/neo4j/v1/compat.py +++ b/neo4j/v1/compat.py @@ -32,9 +32,19 @@ try: unicode except NameError: + # Python 3 + integer = int string = str + def ustr(x): + if isinstance(x, bytes): + return x.decode("utf-8") + elif isinstance(x, str): + return x + else: + return str(x) + def hex2(x): if x < 0x10: return "0" + hex(x)[2:].upper() @@ -42,9 +52,19 @@ def hex2(x): return hex(x)[2:].upper() else: + # Python 2 + integer = (int, long) string = (str, unicode) + def ustr(x): + if isinstance(x, str): + return x.decode("utf-8") + elif isinstance(x, unicode): + return x + else: + return unicode(x) + def hex2(x): x = ord(x) if x < 0x10: diff --git a/neo4j/v1/connection.py b/neo4j/v1/connection.py index a0c6e87bf..37fe2cee2 100644 --- a/neo4j/v1/connection.py +++ b/neo4j/v1/connection.py @@ -33,7 +33,7 @@ from struct import pack as struct_pack, unpack as struct_unpack, unpack_from as struct_unpack_from from .constants import DEFAULT_PORT, DEFAULT_USER_AGENT, KNOWN_HOSTS, MAGIC_PREAMBLE, \ - SECURITY_DEFAULT, SECURITY_TRUST_ON_FIRST_USE + TRUST_DEFAULT, TRUST_ON_FIRST_USE from .compat import hex2 from .exceptions import ProtocolError from .packstream import Packer, Unpacker @@ -398,8 +398,8 @@ def connect(host, port=None, ssl_context=None, **config): der_encoded_server_certificate = s.getpeercert(binary_form=True) if der_encoded_server_certificate is None: raise ProtocolError("When using a secure socket, the server should always provide a certificate") - security = config.get("security", SECURITY_DEFAULT) - if security == SECURITY_TRUST_ON_FIRST_USE: + trust = config.get("trust", TRUST_DEFAULT) + if trust == TRUST_ON_FIRST_USE: store = PersonalCertificateStore() if not store.match_or_trust(host, der_encoded_server_certificate): raise ProtocolError("Server certificate does not match known certificate for %r; check " diff --git a/neo4j/v1/constants.py b/neo4j/v1/constants.py index 238c24ed4..41e5f5732 100644 --- a/neo4j/v1/constants.py +++ b/neo4j/v1/constants.py @@ -31,8 +31,9 @@ MAGIC_PREAMBLE = 0x6060B017 -SECURITY_NONE = 0 -SECURITY_TRUST_ON_FIRST_USE = 1 -SECURITY_VERIFIED = 2 +ENCRYPTED_DEFAULT = True -SECURITY_DEFAULT = SECURITY_TRUST_ON_FIRST_USE +TRUST_ON_FIRST_USE = 0 +TRUST_SIGNED_CERTIFICATES = 1 + +TRUST_DEFAULT = TRUST_ON_FIRST_USE diff --git a/neo4j/v1/session.py b/neo4j/v1/session.py index a5af08d5d..c72478d85 100644 --- a/neo4j/v1/session.py +++ b/neo4j/v1/session.py @@ -33,7 +33,7 @@ class which can be used to obtain `Driver` instances that are used for from .compat import integer, string, urlparse from .connection import connect, Response, RUN, PULL_ALL -from .constants import SECURITY_NONE, SECURITY_VERIFIED, SECURITY_DEFAULT +from .constants import ENCRYPTED_DEFAULT, TRUST_DEFAULT, TRUST_SIGNED_CERTIFICATES from .exceptions import CypherError, ResultError from .typesystem import hydrated @@ -99,11 +99,12 @@ def __init__(self, url, **config): self.config = config self.max_pool_size = config.get("max_pool_size", DEFAULT_MAX_POOL_SIZE) self.session_pool = deque() - self.security = security = config.get("security", SECURITY_DEFAULT) - if security > SECURITY_NONE: + self.encrypted = encrypted = config.get("encrypted", ENCRYPTED_DEFAULT) + self.trust = trust = config.get("trust", TRUST_DEFAULT) + if encrypted: ssl_context = SSLContext(PROTOCOL_SSLv23) ssl_context.options |= OP_NO_SSLv2 - if security >= SECURITY_VERIFIED: + if trust >= TRUST_SIGNED_CERTIFICATES: ssl_context.verify_mode = CERT_REQUIRED ssl_context.load_default_certs(Purpose.SERVER_AUTH) self.ssl_context = ssl_context diff --git a/test/tck/tck_util.py b/test/tck/tck_util.py index d3d2a6549..69ec02b2c 100644 --- a/test/tck/tck_util.py +++ b/test/tck/tck_util.py @@ -18,11 +18,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from neo4j.v1 import GraphDatabase, Relationship, Node, Path, SECURITY_NONE, basic_auth + +from neo4j.v1 import GraphDatabase, Relationship, Node, Path, basic_auth from neo4j.v1.compat import string -driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password"), security=SECURITY_NONE) +driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password"), encrypted=False) + def send_string(text): session = driver.session() diff --git a/test/test_session.py b/test/test_session.py index bd95ce4e6..14b851fe6 100644 --- a/test/test_session.py +++ b/test/test_session.py @@ -23,7 +23,7 @@ from ssl import SSLSocket from mock import patch -from neo4j.v1.constants import SECURITY_NONE, SECURITY_TRUST_ON_FIRST_USE +from neo4j.v1.constants import TRUST_ON_FIRST_USE from neo4j.v1.exceptions import CypherError, ResultError from neo4j.v1.session import GraphDatabase, basic_auth, Record, record from neo4j.v1.typesystem import Node, Relationship, Path @@ -92,10 +92,10 @@ class SecurityTestCase(ServerTestCase): def test_default_session_uses_tofu(self): driver = GraphDatabase.driver("bolt://localhost") - assert driver.security == SECURITY_TRUST_ON_FIRST_USE + assert driver.trust == TRUST_ON_FIRST_USE def test_insecure_session_uses_normal_socket(self): - driver = GraphDatabase.driver("bolt://localhost", auth=auth_token, security=SECURITY_NONE) + driver = GraphDatabase.driver("bolt://localhost", auth=auth_token, encrypted=False) session = driver.session() connection = session.connection assert isinstance(connection.channel.socket, socket) @@ -103,7 +103,7 @@ def test_insecure_session_uses_normal_socket(self): session.close() def test_tofu_session_uses_secure_socket(self): - driver = GraphDatabase.driver("bolt://localhost", auth=auth_token, security=SECURITY_TRUST_ON_FIRST_USE) + driver = GraphDatabase.driver("bolt://localhost", auth=auth_token, encrypted=True, trust=TRUST_ON_FIRST_USE) session = driver.session() connection = session.connection assert isinstance(connection.channel.socket, SSLSocket) @@ -111,7 +111,7 @@ def test_tofu_session_uses_secure_socket(self): session.close() def test_tofu_session_trusts_certificate_after_first_use(self): - driver = GraphDatabase.driver("bolt://localhost", auth=auth_token, security=SECURITY_TRUST_ON_FIRST_USE) + driver = GraphDatabase.driver("bolt://localhost", auth=auth_token, encrypted=True, trust=TRUST_ON_FIRST_USE) session = driver.session() connection = session.connection certificate = connection.der_encoded_server_certificate