M fanboi2/models/_type.py => fanboi2/models/_type.py +1 -1
@@ 4,7 4,7 @@ from sqlalchemy.sql.sqltypes import Enum
BoardStatusEnum = Enum("open", "restricted", "locked", "archived", name="board_status")
-IdentTypeEnum = Enum("none", "ident", "ident_admin", name="ident_type")
+IdentTypeEnum = Enum("none", "ident", "ident_v6", "ident_admin", name="ident_type")
TopicStatusEnum = Enum("open", "locked", "archived", "expired", name="topic_status")
M fanboi2/services/post.py => fanboi2/services/post.py +13 -1
@@ 1,4 1,5 @@
import datetime
+import ipaddress
from sqlalchemy.sql import func
@@ 90,10 91,21 @@ class PostCreateService(object):
ident_type = "none"
if board.settings["use_ident"]:
ident_type = "ident"
+ ident_addr = ip_address
+
+ # Since it's common for IPv6 setup to delegate a /64 from ISP to a home
+ # network, it makes more sense here to always generate ident based on
+ # /64 network instead of individual address.
+ if ipaddress.ip_address(ip_address).version == 6:
+ ident_type = "ident_v6"
+ ident_addr = str(
+ ipaddress.ip_network("%s/64" % (ip_address,), strict=False)
+ )
+
ident = self.identity_svc.identity_with_tz_for(
self.setting_query_svc.value_from_key("app.time_zone"),
board=topic.board.slug,
- ip_address=ip_address,
+ ip_address=ident_addr,
)
post = Post(
M fanboi2/tests/test_services_post.py => fanboi2/tests/test_services_post.py +17 -0
@@ 52,6 52,23 @@ class TestPostCreateService(ModelSessionMixin, unittest.TestCase):
self.assertEqual(topic_meta.post_count, 1)
self.assertIsNotNone(topic_meta.bumped_at)
+ def test_create_ipv6(self):
+ from ..models import Board, Topic, TopicMeta
+
+ board = self._make(Board(slug="foo", title="Foo"))
+ topic = self._make(Topic(board=board, title="Hello", status="open"))
+ self._make(TopicMeta(topic=topic, post_count=0))
+ self.dbsession.commit()
+ post_create_svc = self._make_one()
+ post1 = post_create_svc.create(topic.id, "Hello!", True, "fe80:c9cd::1")
+ self.assertEqual(post1.ip_address, "fe80:c9cd::1")
+ self.assertEqual(post1.ident, "foo,fe80:c9cd::/64")
+ self.assertEqual(post1.ident_type, "ident_v6")
+ post2 = post_create_svc.create(topic.id, "Hello!", True, "fe80:c9cd::96f0:1111")
+ self.assertEqual(post2.ip_address, "fe80:c9cd::96f0:1111")
+ self.assertEqual(post2.ident, "foo,fe80:c9cd::/64")
+ self.assertEqual(post2.ident_type, "ident_v6")
+
def test_create_without_bumped(self):
from ..models import Board, Topic, TopicMeta
A migration/versions/5d4ef3966456_add_ident_v6_ident_type.py => migration/versions/5d4ef3966456_add_ident_v6_ident_type.py +54 -0
@@ 0,0 1,54 @@
+"""add ident_v6 ident type
+
+Revision ID: 5d4ef3966456
+Revises: 06000cefb0bb
+Create Date: 2020-03-30 19:29:21.471805
+"""
+from alembic import op
+from sqlalchemy.dialects.postgresql import ENUM
+import sqlalchemy as sa
+
+
+revision = "5d4ef3966456"
+down_revision = "06000cefb0bb"
+
+
+def alter_enum(enum_name, enum_value, fields=None):
+ op.execute("ALTER TYPE %s RENAME TO %s_1" % (enum_name, enum_name))
+
+ new_enum = sa.Enum(*enum_value, name=enum_name)
+ new_enum.create(op.get_bind(), checkfirst=True)
+ if fields:
+ for table, column in fields:
+ op.alter_column(
+ table,
+ column,
+ type_=new_enum,
+ postgresql_using="%s::text::%s" % (column, enum_name),
+ )
+
+ ENUM(name="%s_1" % enum_name).drop(op.get_bind(), checkfirst=True)
+
+
+def upgrade():
+ alter_enum(
+ "ident_type",
+ ("none", "ident", "ident_v6", "ident_admin"),
+ (
+ ("user", "ident_type"),
+ ("post", "ident_type"),
+ ("post_history", "ident_type"),
+ ),
+ )
+
+
+def downgrade():
+ alter_enum(
+ "ident_type",
+ ("none", "ident", "ident_admin"),
+ (
+ ("user", "ident_type"),
+ ("post", "ident_type"),
+ ("post_history", "ident_type"),
+ ),
+ )