diff --git a/irrd/updates/handler.py b/irrd/updates/handler.py index dea13b7..6646ac4 100644 --- a/irrd/updates/handler.py +++ b/irrd/updates/handler.py @@ -9,9 +9,10 @@ from irrd.conf import get_setting from irrd.rpsl.rpsl_objects import RPSLMntner from irrd.storage.database_handler import DatabaseHandler -from irrd.storage.models import AuthoritativeChangeOrigin, AuthUser +from irrd.storage.models import AuthoritativeChangeOrigin, AuthUser, AuthMntner, AuthPermission, RPSLDatabaseObject from irrd.storage.queries import RPSLDatabaseQuery from irrd.utils import email +from ..storage.orm_provider import ORMSessionProvider from ..utils.validators import RPSLChangeSubmission, RPSLSuspensionSubmission from .parser import ChangeRequest, SuspensionRequest, parse_change_requests @@ -40,6 +41,7 @@ def load_text_blob( self.database_handler = DatabaseHandler() self.request_meta = request_meta if request_meta else {} self._pgp_key_id = self._resolve_pgp_key_id(pgp_fingerprint) if pgp_fingerprint else None + self.new_internal_auths = [] reference_validator = ReferenceValidator(self.database_handler) auth_validator = AuthValidator( @@ -56,6 +58,7 @@ def load_text_blob( self._handle_change_requests(change_requests, reference_validator, auth_validator) self.database_handler.commit() self.database_handler.close() + self._handle_new_internal_auths(auth_validator) return self def load_change_submission( @@ -68,6 +71,7 @@ def load_change_submission( ): self.database_handler = DatabaseHandler() self.request_meta = request_meta if request_meta else {} + self.new_internal_auths = [] reference_validator = ReferenceValidator(self.database_handler) auth_validator = AuthValidator(self.database_handler, origin, remote_ip=remote_ip) @@ -106,6 +110,7 @@ def load_change_submission( self._handle_change_requests(change_requests, reference_validator, auth_validator) self.database_handler.commit() self.database_handler.close() + self._handle_new_internal_auths(auth_validator) return self def load_suspension_submission( @@ -113,6 +118,7 @@ def load_suspension_submission( ): self.database_handler = DatabaseHandler() self.request_meta = request_meta if request_meta else {} + self.new_internal_auths = [] reference_validator = ReferenceValidator(self.database_handler) auth_validator = AuthValidator(self.database_handler) @@ -136,6 +142,7 @@ def load_suspension_submission( self._handle_change_requests(change_requests, reference_validator, auth_validator) self.database_handler.commit() self.database_handler.close() + self._handle_new_internal_auths(auth_validator) return self def _handle_change_requests( @@ -190,8 +197,45 @@ def _handle_change_requests( if result.is_valid(): result.save() + # Insert internal authentication for newly created mntners + if result.request_type == UpdateRequestType.CREATE and isinstance(result.rpsl_obj_new, RPSLMntner) \ + and result.rpsl_obj_new.has_internal_auth(): + self.new_internal_auths.append(result.rpsl_obj_new) + self.results = change_requests + def _handle_new_internal_auths(self, auth_validator: AuthValidator): + user = auth_validator.user() + if not user: + return + + session_provider = ORMSessionProvider() + + for rpsl_obj in self.new_internal_auths: + query = session_provider.session.query(RPSLDatabaseObject) + query = query.filter( + RPSLDatabaseObject.rpsl_pk == rpsl_obj.pk(), + RPSLDatabaseObject.source == rpsl_obj.source(), + RPSLDatabaseObject.object_class == "mntner", + ) + mntner_obj = session_provider.run_sync(query.one) + new_auth_mntner = AuthMntner( + rpsl_mntner_pk=rpsl_obj.pk(), + rpsl_mntner_obj_id=str(mntner_obj.pk), + rpsl_mntner_source=rpsl_obj.source(), + ) + session_provider.session.add(new_auth_mntner) + session_provider.session.commit() + new_permission = AuthPermission( + user_id=str(user.pk), + mntner_id=str(new_auth_mntner.pk), + user_management=True, + ) + session_provider.session.add(new_permission) + session_provider.session.commit() + + session_provider.commit_close() + def _resolve_pgp_key_id(self, pgp_fingerprint: str) -> Optional[str]: """ Find a PGP key ID for a given fingerprint. diff --git a/irrd/updates/parser.py b/irrd/updates/parser.py index 5bada75..00c8bd9 100644 --- a/irrd/updates/parser.py +++ b/irrd/updates/parser.py @@ -325,7 +325,7 @@ def validate(self) -> bool: self.status = UpdateRequestStatus.ERROR_PARSING return False if self.rpsl_obj_new and self.request_type and self.request_type != UpdateRequestType.DELETE: - rules_result = self.rules_validator.validate(self.rpsl_obj_new, self.request_type) + rules_result = self.rules_validator.validate(self.rpsl_obj_new, self.request_type, self.auth_validator.user()) self.info_messages += rules_result.info_messages self.error_messages += rules_result.error_messages if not rules_result.is_valid(): diff --git a/irrd/updates/validators.py b/irrd/updates/validators.py index 1f9ca46..ce60c61 100644 --- a/irrd/updates/validators.py +++ b/irrd/updates/validators.py @@ -14,7 +14,7 @@ PROTECTED_NAME_OBJECT_CLASSES, RPSLMntner, RPSLSet, - rpsl_object_from_text, + rpsl_object_from_text, RPSLAutNum, RPSLAsBlock, RPSLInetnum, RPSLInet6Num, ) from irrd.storage.database_handler import DatabaseHandler from irrd.storage.models import ( @@ -317,6 +317,9 @@ def __init__( self.keycert_obj_pk = keycert_obj_pk self._internal_authenticated_user = internal_authenticated_user + def user(self) -> Optional[AuthUser]: + return self._internal_authenticated_user + def pre_approve(self, presumed_valid_new_mntners: List[RPSLMntner]) -> None: """ Pre-approve certain maintainers that are part of this batch of updates. @@ -354,7 +357,7 @@ def process_auth( logger.info("Found valid override password.") return result - mntners_new = rpsl_obj_new.parsed_data["mnt-by"] + mntners_new = rpsl_obj_new.parsed_data.get("mnt-by", []) logger.debug(f"Checking auth for new object {rpsl_obj_new}, mntners in new object: {mntners_new}") new_mntners_result = self._check_mntners(rpsl_obj_new, mntners_new, source) if not new_mntners_result.valid: @@ -393,9 +396,6 @@ def process_auth( result.error_messages.add("New inet(6)num objects must be added by an administrator.") if isinstance(rpsl_obj_new, RPSLMntner): - if not rpsl_obj_current: - result.error_messages.add("New mntner objects must be added by an administrator.") - return result # Dummy auth values are only permitted in existing objects if rpsl_obj_new.has_dummy_auth_value(): if len(self.passwords) == 1: @@ -420,7 +420,7 @@ def process_auth( self._mntner_matches_internal_auth(rpsl_obj_new, rpsl_obj_new.pk(), source), # API keys are not checked here, as they can never be used on RPSLMntner ] - ): + ) and not rpsl_obj_new.pk() in self._pre_approved: result.error_messages.add("Authorisation failed for the auth methods on this mntner object.") if isinstance(rpsl_obj_new, RPSLAutNum) or isinstance(rpsl_obj_new, RPSLAsBlock): @@ -718,7 +718,7 @@ class RulesValidator: def __init__(self, database_handler: DatabaseHandler) -> None: self.database_handler = database_handler - def validate(self, rpsl_obj: RPSLObject, request_type: UpdateRequestType) -> ValidatorResult: + def validate(self, rpsl_obj: RPSLObject, request_type: UpdateRequestType, user: Optional[AuthUser]) -> ValidatorResult: result = ValidatorResult() if ( @@ -738,9 +738,22 @@ def validate(self, rpsl_obj: RPSLObject, request_type: UpdateRequestType) -> Val f"This maintainer is migrated and must include the {RPSL_MNTNER_AUTH_INTERNAL} method." ) elif not is_migrated and has_internal_auth: + if not user: + result.error_messages.add( + "This maintainer is not migrated, and therefore can not use the " + f"{RPSL_MNTNER_AUTH_INTERNAL} method. " + f"Login to create a new maintainer with {RPSL_MNTNER_AUTH_INTERNAL}" + ) + elif request_type != UpdateRequestType.CREATE: + result.error_messages.add( + "This maintainer is not migrated, and therefore can not use the " + f"{RPSL_MNTNER_AUTH_INTERNAL} method." + ) + + if rpsl_obj.rpsl_object_class in ("person", "role"): + if not rpsl_obj.pk().endswith(f"-{rpsl_obj.source()}"): result.error_messages.add( - "This maintainer is not migrated, and therefore can not use the" - f" {RPSL_MNTNER_AUTH_INTERNAL} method." + f"nic-hdls in {rpsl_obj.source()} must end with -{rpsl_obj.source()}" ) return result