def save_model(self, request, obj, form, change): MySQLDatabase = cache.get_model('mysql_manager', 'MySQLDatabase') if not change: # Database creation form # The ``created_by`` attribute is set once at creation time obj.created_by = request.user MySQLDatabase.objects.create_database(obj.name, obj.charset_name, obj.collation_name) if obj.user: MySQLDatabase.objects.grant_privileges(obj.name, obj.user.name) else: # This is the change form if 'charset_name' in form.changed_data or 'collation_name' in form.changed_data: MySQLDatabase.objects.change_specification(obj.name, obj.charset_name, obj.collation_name) if 'user' in form.changed_data: # First revoke all privileges on the old database. old_user_id = form.initial.get('user', None) if old_user_id is not None: # old_user_id can be none in cases where it has never been set before. MySQLUser = cache.get_model('mysql_manager', 'MySQLUser') old_user = MySQLUser.objects.get(id__exact=old_user_id) MySQLDatabase.objects.revoke_privileges(obj.name, old_user.name) if obj.user: # Now grant all privileges to the new owner MySQLDatabase.objects.grant_privileges(obj.name, obj.user.name) # Save the model obj.save()
def get_for_picking(self, ct): """ Returns class instance of a given model ContentType""" if type(ct) is not ContentType: raise TypeError, "cannot call get_for_picking without a valid ContentType, called with {0:>s}".format model = app_cache.get_model(ct.app_label, ct.model) if not model: try: app_cache.write_lock.acquire() module = app_cache.load_app('libscampi.contrib.{0:>s}'.format(ct.app_label)) app_cache.write_lock.release() model = app_cache.get_model(ct.app_label, ct.model) except ImportError: raise NameError, "cannot get ({0:>s}, {1:>s}) from any path".format(ct.app_label, ct.model) if not self.is_registered(model): raise NameError, "({0:>s}, {1:>s}) is not registered for picking".format(ct.app_label, ct.model) try: fs = self.get_registration_info(model)() except PickerError: raise NameError, "({0:>s}, {1:>s}) has no filter set".format(ct.app_label, ct.model) return model, fs
def template_create_zone_view(request, template_id): """Create zone from template. Accepts a template ID. An intermediate page asking for the origin of the new zone is used. """ # Permission check on models. if not request.user.has_perms([ 'powerdns_manager.add_domain', ]): messages.error(request, 'Insufficient permissions for this action.') return HttpResponseRedirect(reverse('admin:powerdns_manager_zonetemplate_changelist')) if request.method == 'POST': origin = request.POST.get('origin') # Get the models ZoneTemplate = cache.get_model('powerdns_manager', 'ZoneTemplate') Domain = cache.get_model('powerdns_manager', 'Domain') template_obj = ZoneTemplate.objects.get(id=template_id) template_obj_display = force_unicode(template_obj) # Check template ownership. if request.user != template_obj.created_by: messages.error(request, 'Permission denied for template: %s' % template_obj_display) return HttpResponseRedirect(reverse('admin:powerdns_manager_zonetemplate_changelist')) else: # Replace placeholder with origin in the template content. zonetext = template_obj.content.replace('#origin#', origin) process_zone_file(origin, zonetext, request.user) messages.info(request, "Successfully created zone '%s' from template '%s'." % (origin, template_obj.name)) # Redirect to the new zone's change form. domain_obj = Domain.objects.get(name=origin) return HttpResponseRedirect(reverse('admin:powerdns_manager_domain_change', args=(domain_obj.id,))) # Create log entry # LogEntry.objects.log_action( # user_id = request.user.pk, # content_type_id = ContentType.objects.get_for_model(obj).pk, # object_id = obj.pk, # object_repr = obj_display, # action_flag = CHANGE # ) else: form = TemplateOriginForm() info_dict = { 'form': form, 'template_id': template_id, } return render_to_response( 'powerdns_manager/template/create_zone.html', info_dict, context_instance=RequestContext(request))
def test_models_py(self): """ Tests that the models in the models.py file were loaded correctly. """ self.assertEqual(cache.get_model("app_cache", "TotallyNormal"), TotallyNormal) self.assertEqual(cache.get_model("app_cache", "SoAlternative"), None) self.assertEqual(new_app_cache.get_model("app_cache", "TotallyNormal"), None) self.assertEqual(new_app_cache.get_model("app_cache", "SoAlternative"), SoAlternative)
def main(): parser = OptionParser() parser.add_option("-f", "--fichero", dest="fichero", help="fichero a crear.") parser.add_option("-m", "--modelo", dest="modelo", help="modelo a exportar") (options, dummy) = parser.parse_args() if not options.fichero or not options.modelo: parser.print_help() return print "\n\n" fichero = open(options.fichero,'r') model = cache.get_model("rtg", options.modelo) campos = fichero.readline().split(";") for linea in fichero: print linea datos = linea.split(";") instancia = model() s = zip(campos,datos) for (campo,dato) in s: print campo,dato setattr(instancia,campo,dato) instancia.save() fichero.close()
def __new__(cls, *args, **kwargs): if 'app_label' in kwargs['queue_options']: if 'modl_name' in kwargs['queue_options']: from django.db.models.loading import cache mgr_attr = kwargs['queue_options'].get('manager', "objects") ModlCls = cache.get_model( app_label=kwargs['queue_options'].get('app_label'), model_name=kwargs['queue_options'].get('modl_name')) if ModlCls is not None: mgr_instance = getattr(ModlCls, mgr_attr) mgr_instance.runmode = kwargs.pop('runmode', None) mgr_instance.queue_name = kwargs.pop('queue_name') mgr_instance.queue_options = {} mgr_instance.queue_options.update(kwargs.pop('queue_options', {})) return mgr_instance else: return QueueBase() else: raise ImproperlyConfigured( "DatabaseQueueProxy's queue configuration requires the name of a model class to be specified in in 'modl_name'.") else: raise ImproperlyConfigured( "DatabaseQueueProxy's queue configuration requires an app specified in 'app_label', in which the definition for a model named 'modl_name' can be found.")
def save_model(self, request, obj, form, change): MySQLUser = cache.get_model('mysql_manager', 'MySQLUser') password = form.cleaned_data.get('password1') if not change: # User creation form assert password != '', 'A password is mandatory for new accounts' # The ``created_by`` attribute is set once at creation time obj.created_by = request.user MySQLUser.objects.create_user(obj.name, password) MySQLUser.objects.set_limits(obj.name, obj.max_queries_per_hour, obj.max_updates_per_hour, obj.max_connections_per_hour, obj.max_user_connections) else: # This is the change form if 'max_queries_per_hour' in form.changed_data or \ 'max_updates_per_hour' in form.changed_data or \ 'max_connections_per_hour' in form.changed_data or \ 'max_user_connections' in form.changed_data: MySQLUser.objects.set_limits(obj.name, obj.max_queries_per_hour, obj.max_updates_per_hour, obj.max_connections_per_hour, obj.max_user_connections) if password: MySQLUser.objects.change_password(obj.name, password) # Save the model obj.save()
def main(): parser = OptionParser() parser.add_option("-f", "--fichero", dest="fichero", help="fichero a crear.") parser.add_option("-m", "--modelo", dest="modelo", help="modelo a exportar") (options, dummy) = parser.parse_args() if not options.fichero or not options.modelo: parser.print_help() return print "\n\n" fichero = open(options.fichero,'w') model = cache.get_model("rtg", options.modelo) writer = csv.writer(fichero,delimiter=';') # Write headers to CSV file headers = [] for field in model._meta.fields: #IGNORE:W0212 headers.append(field.name) writer.writerow(headers) # Write data to CSV file for obj in model.objects.all(): row = [] for field in headers: if field in headers: val = getattr(obj, field) if callable(val): val = val() # if isinstance(val,basestring): # val = val.encode("ISO-8859-1") row.append(val) writer.writerow(row) fichero.close()
def dispatch(self, *args, **kwargs): self.model_name = kwargs["model"] # TODO remove hard-coded 'main' app name here self.model = cache.get_model("main", self.model_name) if self.model is None: raise ValueError("No such model: %s" % self.model_name) return super(ModelNameInUrlMixin, self).dispatch(*args, **kwargs)
def dequeue(self, queued_signal=None): """ Deserialize and execute a signal, either from the queue or as per the contents of the queued_signal kwarg. If queued_signal contains a serialized signal call datastructure,* dequeue() will deserialize and execute that serialized signal without popping the queue. If queued_signal is None, it will call retrieve() to pop the queue for the next signal, which it will execute if one is returned successfully. * See the QueueBase docstring for an example. """ if queued_signal is None: queued_signal = self.retrieve() logg.info("Dequeueing signal: %s" % queued_signal) signal_dict = queued_signal.get('signal') sender_dict = queued_signal.get('sender') regkey, name = signal_dict.items()[0] sender = None # specifying a sender is optional. if sender_dict is not None: try: sender = cache.get_model(str(sender_dict['app_label']), str(sender_dict['modl_name'])) except (KeyError, AttributeError), err: sender = None
def reset_database(modeladmin, request, queryset): MySQLDatabase = cache.get_model('mysql_manager', 'MySQLDatabase') n = queryset.count() for db_obj in queryset: # Store the database info in variables db_name = db_obj.name db_user_obj = db_obj.user db_charset_name = db_obj.charset_name db_collation_name = db_obj.collation_name db_created_by_obj = db_obj.created_by # Delete the current database db_obj.delete() # Create a new database object with the stored info new_db_obj = MySQLDatabase( name = db_name, user = db_user_obj, charset_name = db_charset_name, collation_name = db_collation_name, created_by = db_created_by_obj ) # First create the MySQL database on the MySQL server MySQLDatabase.objects.create_database(db_name, db_charset_name, db_collation_name) # Grant privileges on the user MySQLDatabase.objects.grant_privileges(db_name, db_user_obj.name) # Now save the Django object new_db_obj.save() if n: messages.info(request, 'Successful reset of %d database(s)' % n)
def render(self, context): limit = None model_lookup = self.model.resolve(context) incorrect_value = ValueError( "'%s' does not result in a model. Is it correct?" % model_lookup ) try: model = app_cache.get_model(*model_lookup.split(".")) except TypeError: raise incorrect_value else: if model is None: raise incorrect_value if self.limit is not None: limit = self.limit.resolve(context) objs = fetch_top_objects(model, self.time_limit) if limit is not None: objs = objs[:limit] context[self.context_var] = objs return u""
def drop_database(sender, **kwargs): MySQLDatabase = cache.get_model('mysql_manager', 'MySQLDatabase') instance = kwargs['instance'] # First revoke the privileges of the user on this database MySQLDatabase.objects.revoke_privileges(instance.name, instance.user.name) # Then delete the database. MySQLDatabase.objects.drop_database(instance.name)
def send_template_email(recipients, title_template, body_template, context, language): """Sends e-mail using templating system""" site_name = getattr(settings, 'SITE_NAME', 'Please define settings.SITE_NAME') domain = getattr(settings, 'SITE_URL', None) if domain is None: Site = cache.get_model('sites', 'Site') current_site = Site.objects.get_current() site_name = current_site.name domain = current_site.domain context.update({'site_name' : site_name, 'site_domain': domain}) if language is not None: translation.activate(language) mail_title_template = loader.get_template(title_template) mail_body_template = loader.get_template(body_template) title = mail_title_template.render(context) body = mail_body_template.render(context) try: email_from = getattr(settings, 'DEFAULT_FROM_EMAIL') except AttributeError: raise ImproperlyConfigured('DEFAULT_FROM_EMAIL setting needed for sending e-mails') mail.send_mail(title, body, email_from, recipients) if language is not None: translation.deactivate() email_logger.info(u"Email (%s) sent to %s\nTitle: %s\n%s\n\n" % (language, recipients, title, body))
def email_verify_view(request, verification_key, extra_context={}, success_url='email_change_complete', template_name='email_change/email_verify.html'): """ """ EmailChangeRequest = cache.get_model('email_change', 'EmailChangeRequest') context = RequestContext(request, extra_context) try: ecr = EmailChangeRequest.objects.get( user=request.user, verification_key=verification_key) except EmailChangeRequest.DoesNotExist: # Return failure response return render_to_response(template_name, context_instance=context) else: # Check if the email change request has expired if ecr.has_expired(): ecr.delete() # Return failure response return render_to_response(template_name, context_instance=context) # Success. Replace the user's email with the new email request.user.email = ecr.email request.user.save() # Delete the email change request ecr.delete() # Redirect to success URL return redirect(success_url)
def send_template_email(recipients, title_template, body_template, context, language): """Sends e-mail using templating system""" send_emails = getattr(settings, 'SEND_PLANS_EMAILS', True) if not send_emails: return site_name = getattr(settings, 'SITE_NAME', 'Please define settings.SITE_NAME') domain = getattr(settings, 'SITE_URL', None) if domain is None: Site = cache.get_model('sites', 'Site') current_site = Site.objects.get_current() site_name = current_site.name domain = current_site.domain context.update({'site_name': site_name, 'site_domain': domain}) if language is not None: original_language = translation.get_language() translation.activate(language) mail_title_template = loader.get_template(title_template) mail_body_template = loader.get_template(body_template) title = mail_title_template.render(context) body = mail_body_template.render(context) email_from = getattr(settings, 'DEFAULT_FROM_EMAIL') mail.send_mail(title, body, email_from, recipients) if language is not None: translation.activate(original_language) email_logger.info(u"Email ({0}) sent to {1}\nTitle: {2}\n{3}\n\n".format( language, recipients, title, body))
def handle(self, *origins, **options): outdir = os.path.abspath(options.get('directory')) export_all = options.get('all') verbosity = int(options.get('verbosity', 1)) if export_all and len(origins) > 0: raise CommandError('No origins should be specified when the --all switch is used.') Domain = cache.get_model('powerdns_manager', 'Domain') if export_all: origins = [d.name for d in Domain.objects.all()] for origin in origins: try: data = generate_zone_file(origin) except Domain.DoesNotExist: sys.stderr.write('error: zone not found: %s\n' % origin) sys.stderr.flush() else: path = os.path.join(outdir, '%s.zone' % origin) f = open(path, 'w') f.write(data) f.close() if verbosity: sys.stdout.write('success: %s\n' % origin) sys.stdout.flush()
def save_model(self, request, obj, form, change): PgUser = cache.get_model('postgresql_manager', 'PgUser') password = form.cleaned_data.get('password1') if not change: # User creation form assert password != '', 'A password is mandatory for new accounts' # The ``created_by`` attribute is set once at creation time obj.created_by = request.user attrs = 'LOGIN' if not obj.is_active: attrs = 'NOLOGIN' PgUser.objects.create_role(obj.name, password, attrs) else: # This is the change form if 'name' in form.changed_data: PgUser.objects.rename_role(form.initial['name'], obj.name) if 'connlimit' in form.changed_data: PgUser.objects.limit_connections(obj.name, obj.connlimit) if 'is_active' in form.changed_data: PgUser.objects.lock_unlock_role(obj.name, obj.is_active) if password: PgUser.objects.change_password(obj.name, password) # Save the model obj.save()
def _group_gfk_field(self, model, join=None, field_name=None): opts = model._meta if field_name is None: field_name = "group" if join is not None: # see if we can get the model where the field actually lives parts = join.split(LOOKUP_SEP) for name in parts: f, model, direct, m2m = opts.get_field_by_name(name) # not handling the model is not None case (proxied models I think) if direct: if m2m or f.rel: opts = f.rel.to._meta else: break else: opts = f.opts try: field = [f for f in opts.virtual_fields if f.name == field_name][0] except IndexError: from django.db.models.loading import cache as app_cache model = app_cache.get_model(opts.app_label, opts.module_name) raise LookupError("Unable to find generic foreign key named '%s' " "on %r\nThe model may have a different name or it does not " "exist." % ( field_name, model, )) return field
def __init__(self, start_id, end_id, model, *args, **kwargs): self.shard_id = 1 self.start_id = start_id self.end_id = end_id self.raw_model = model app, model = self.raw_model.split('.') self.model = model_cache.get_model(app, model) super(DjangoInputReader, self).__init__(*args, **kwargs)
def tag_list_view(request): app_label, model_class = settings.TAGGIT_AUTOCOMPLETE_TAG_MODEL.split('.') Tag = cache.get_model(app_label, model_class) try: tags = Tag.objects.all().values_list('name', flat=True) #TODO: add filtering except MultiValueDictKeyError: tags = [] return HttpResponse(simplejson.dumps(list(tags)), mimetype='application/json')
def __call__(cls, request): app_name = request.GET.get('app_name','') model_name = request.GET.get('model','') model = APPCACHE.get_model(app_name, model_name) if model: return HttpResponse(simplejson.dumps(cls.get_context(model)), mimetype='application/json') return HttpResponse(simplejson.dumps({}), mimetype='application/json')
def tag_list_view(request): app_label, model_class = TAGGIT_AUTOCOMPLETE_TAG_MODEL.split(".") Tag = cache.get_model(app_label, model_class) try: tags = Tag.objects.filter(name__istartswith=request.GET["term"]).values_list("name", flat=True) except MultiValueDictKeyError: tags = [] return HttpResponse(json.dumps(list(tags), ensure_ascii=False), mimetype="application/json")
def tag_list_view(request): app_label, model_class = settings.TAGGIT_AUTOCOMPLETE_TAG_MODEL.split('.') Tag = cache.get_model(app_label, model_class) try: tags = Tag.objects.filter(name__istartswith=request.GET['q']).values_list('name', flat=True) except MultiValueDictKeyError: tags = [] return HttpResponse('\n'.join(tags), mimetype='text/plain')
def flush_signal_queue(self, apps, options): """ Flushes the named signal queue, executing all enqueued signals. """ from django.conf import settings from signalqueue import SQ_RUNMODES as runmodes from signalqueue.worker import backends from signalqueue.models import log_exceptions queue_name = options.get('queue_name') queues = backends.ConnectionHandler(settings.SQ_QUEUES, runmodes['SQ_ASYNC_MGMT']) if not queue_name in queues: self.echo("\n--- No definition found for a queue named '%s'" % (queue_name,), color=16) self.echo("\n--- Your defined queues have these names: '%s'" % ("', '".join(queues.keys()),), color=16) self.echo("\n>>> Exiting ...\n\n", color=16) sys.exit(2) queue = queues[queue_name] try: queue_available = queue.ping() except: self.echo("\n--- Can't ping the backend for %s named '%s'" % (queue.__class__.__name__, queue_name), color=16) self.echo("\n--- Is the server running?", color=16) self.echo("\n>>> Exiting ...\n\n", color=16) sys.exit(2) if not queue_available: self.echo("\n--- Can't ping the backend for %s named '%s'" % (queue.__class__.__name__, queue_name), color=16) self.echo("\n--- Is the server running?", color=16) self.echo("\n>>> Exiting ...\n\n", color=16) sys.exit(2) self.echo("\n>>> Flushing signal queue '%s' -- %s enqueued signals total" % ( queue.queue_name, queue.count()), color=31) from django.db.models.loading import cache if queue.count() > 0: for signalblip in queue: #self.echo("\n>>> Signal: ", color=31) #self.echo("\n%s" % pformat(signalblip), color=31) sender_dict = signalblip.get('sender') sender = cache.get_model(str(sender_dict['app_label']), str(sender_dict['modl_name'])) signal = signalblip.get('signal') self.echo(">>> Processing signal sent by %s.%s: %s.%s" % ( sender._meta.app_label, sender.__name__, signal.keys()[0], signal.values()[0]), color=31) with log_exceptions(queue_name=queue_name): queue.dequeue(queued_signal=signalblip) self.echo(">>> Done flushing signal queue '%s' -- %s enqueued signals remaining" % ( queue.queue_name, queue.count()), color=31) self.echo("\n")
def get_custom_model_class(custom_model_name): ''' Returns class for abstract model Args: custom_model_name (str): Custom model name in format 'app_name.CustomModel' ''' return cache.get_model(*custom_model_name.split('.'))
def toggle(request, app, model, id): model = cache.get_model(app, model) obj = model.objects.get(pk=id) status = _toggle(request.user, obj) if status.__class__.__name__ == 'Follow': messages.success(request, 'You are now following %s and will receive regular updates on their activity.' % obj) else: messages.warning(request, 'You are no longer following %s and will not receive any updates on their activity.' % obj) return status
def _get_from_cache(self, link): StoredOEmbedResponse = cache.get_model('oembed_works', 'StoredOEmbedResponse') link_hash = md5(link).hexdigest() try: stored_response = StoredOEmbedResponse.objects.get(link_hash=link_hash) except StoredOEmbedResponse.DoesNotExist: return None else: return stored_response.get_response_object()
def remap(cls, intermediate): from django.db.models.loading import cache pk = intermediate.get('obj_id') ModCls = cache.get_model( intermediate.get('app_label'), intermediate.get('modl_name')) if ModCls: if pk is not -1: return ModCls.objects.get(pk=pk) return None
def unfollow(request, app, model, id, user_id=None): if not user_id: user_id = request.user.id model = cache.get_model(app, model) obj = model.objects.get(pk=id) follow = Follow.objects.get_follows(obj).get(user__id=user_id) if follow.user == request.user or follow.folowing == request.user: follow.delete() redirect_to = redirect_to_referer(request) return HttpResponseRedirect(redirect_to)
class BaseTabularRecordInline(admin.TabularInline): RR_TYPE = '__OVERRIDE__' form = '__OVERRIDE__' model = cache.get_model('powerdns_manager', 'Record') extra = 0 fields = ('name', 'ttl', 'content') def __init__(self, *args, **kwargs): self.verbose_name = '%s Resource Record' % self.RR_TYPE self.verbose_name_plural = '%s Resource Records' % self.RR_TYPE super(BaseTabularRecordInline, self).__init__(*args, **kwargs) def queryset(self, request): """Return only RR_TYPE records""" qs = super(BaseTabularRecordInline, self).queryset(request) return qs.filter(type=self.RR_TYPE).order_by('name')
def clean_clone_domain_name(self): clone_domain_name = self.cleaned_data.get('clone_domain_name') # 1) Check for valid characters validate_hostname(clone_domain_name, supports_cidr_notation=True) # 2) Check for uniqueness Domain = cache.get_model('powerdns_manager', 'Domain') try: domain_obj = Domain.objects.get(name=clone_domain_name) except Domain.DoesNotExist: pass else: raise forms.ValidationError('A zone with this name already exists. Please enter a new domain name.') return clone_domain_name
def update_serial(self): """Updates the serial of the zone (SOA record). SOA content: primary hostmaster serial refresh retry expire default_ttl """ Record = cache.get_model('powerdns_manager', 'Record') try: soa_rr = Record.objects.get(domain=self, type='SOA') except Record.DoesNotExist: raise Exception('SOA Resource Record does not exist.') else: bits = soa_rr.content.split() bits[2] = str(generate_serial(serial_old=bits[2])) soa_rr.content = ' '.join(bits) soa_rr.save()
class SoaRecordInline(admin.StackedInline): model = cache.get_model('powerdns_manager', 'Record') form = SoaRecordModelForm # Show exactly one form extra = 1 max_num = 1 verbose_name = 'SOA Resource Record' verbose_name_plural = 'SOA Resource Record' # Only one SOA RR per zone # The ``name`` field is not available for editing. It is always set to the # name of the domain in ``forms.SoaRecordModelForm.save()`` method. fields = ('ttl', 'primary', 'hostmaster', 'serial', 'refresh', 'retry', 'expire', 'default_ttl') can_delete = False def queryset(self, request): """Return only SOA records""" qs = super(SoaRecordInline, self).queryset(request) return qs.filter(type='SOA')
def get_minimum_ttl(self): """Returns the minimum TTL. The SOA record of the zone is retrieved and the minimum TTL is extracted from the ``content`` field. If a SOA record does not exist for this zone, PDNS_DEFAULT_RR_TTL is returned from the settings.. """ Record = cache.get_model('powerdns_manager', 'Record') try: soa_rr = Record.objects.get(domain=self, type='SOA') except Record.DoesNotExist: return settings.PDNS_DEFAULT_RR_TTL else: return soa_rr.content.split()[-1]
class EmptyNonTerminalRecordInline(admin.TabularInline): """Special inline for empty non-terminals supported by PowerDNS 3.2. See: http://doc.powerdns.com/dnssec-modes.html#dnssec-direct-database """ model = cache.get_model('powerdns_manager', 'Record') extra = 0 verbose_name = 'Empty Non-Terminal Resource Record' verbose_name_plural = 'Empty Non-Terminal Resource Record' # Only one SOA RR per zone fields = ('name', 'type', 'content', 'ttl', 'prio', 'auth', 'ordername', 'change_date') readonly_fields = ('name', 'type', 'content', 'ttl', 'prio', 'auth', 'ordername', 'change_date') can_delete = False def queryset(self, request): """Return only Empty Non-Terminal records""" qs = super(EmptyNonTerminalRecordInline, self).queryset(request) return qs.filter(type__isnull=True)
def clean_api_key(self): """Checks the provided API key. 1) The key must contain [A-Z0-9] 2) A dynamic zone must be configured with the supplied key """ api_key = self.cleaned_data.get('api_key') if not re.match('^[A-Z0-9]+$', api_key): raise forms.ValidationError('Invalid API key') DynamicZone = cache.get_model('powerdns_manager', 'DynamicZone') try: DynamicZone.objects.get(api_key__exact=api_key) except DynamicZone.DoesNotExist: raise forms.ValidationError('Invalid API key') else: return api_key
def get_geojson(request): """View used by the map javascript to fetch geojson data for each map tile. This view receives some parameters via GET request and returns a geojson reponse. Params: bounds: string of the form "lat_lo,lng_lo,lat_hi,lng_hi", where "lo" corresponds to the southwest corner of the bounding box, while "hi" corresponds to the northeast corner of that box. zoom: the map zoom level. models: a list of model to filter, separated by comma, of the form "app_name.ModelNamel". project - the id of the the project with which the filtered objects should have ralations. (Optional) """ bounds = request.GET.get('bounds', None) zoom = int(request.GET.get('zoom', 13)) models = request.GET.get('models', '') project = request.GET.get('project', None) if not bounds and not project: return HttpResponseBadRequest(to_json({'error': 'Invalid query'}), mimetype="application/x-javascript") if bounds: x1, y2, x2, y1 = [float(i) for i in bounds.split(',')] polygon = Polygon(((x1, y1), (x1, y2), (x2, y2), (x2, y1), (x1, y1))) intersects_polygon = (Q(points__intersects=polygon) | Q(lines__intersects=polygon) | Q(polys__intersects=polygon)) else: intersects_polygon = Q() models = [cache.get_model(*m.split('.')) for m in models.split(',') if m] d = _fetch_geo_objects(intersects_polygon, zoom, models, project) l = [] for objs in d.values(): l.extend(objs) geojson = create_geojson(l) return HttpResponse(to_json(geojson), mimetype="application/x-javascript")
def general_search(request, app_id, model_id): if not admin.site.has_permission(request): return http.HttpResponseForbidden() model = app_cache.get_model(app_id, model_id) if not model: return http.Http404() limit = 10 obj = admin.site._registry[model] ChangeList = obj.get_changelist(request) # This is a hideous api, but uses the builtin admin search_fields API. # Expecting this to get replaced by ES so soon, that I'm not going to lose # too much sleep about it. cl = ChangeList(request, obj.model, [], [], [], [], obj.search_fields, [], limit, [], obj) qs = cl.get_query_set() # Override search_fields_response on the ModelAdmin object # if you'd like to pass something else back to the front end. lookup = getattr(obj, 'search_fields_response', None) return [{'value':o.pk, 'label':getattr(o, lookup) if lookup else str(o)} for o in qs[:limit]]
def response_change(self, request, obj): """Determines the HttpResponse for the change_view stage. Extends admin.ModelAdmin's ``response_change()`` method to support the domain tools. """ # See template ``powerdns_manager/domain_change_form.html`` if "_transfer" in request.POST: return HttpResponseRedirect(reverse('zone_transfer', args=(obj.id,))) elif "_clone" in request.POST: return HttpResponseRedirect(reverse('zone_clone', args=(obj.id,))) elif "_set_rr_ttl" in request.POST: return HttpResponseRedirect(reverse('zone_set_ttl', args=(obj.id,))) elif "_reset_api_key" in request.POST: Domain = cache.get_model('powerdns_manager', 'Domain') qs = Domain.objects.filter(id=obj.id) reset_api_key(self, request, qs) return super(DomainAdmin, self).response_change(request, obj)
def send_template_email(recipients, title_template, body_template, context, language): """Sends e-mail using templating system""" site_name = getattr(settings, 'SITE_NAME', 'Please define settings.SITE_NAME') domain = getattr(settings, 'SITE_URL', None) if domain is None: Site = cache.get_model('sites', 'Site') current_site = Site.objects.get_current() site_name = current_site.name domain = current_site.domain context.update({'site_name': site_name, 'site_domain': domain}) if language is not None: translation.activate(language) mail_title_template = loader.get_template(title_template) mail_body_template = loader.get_template(body_template) title = mail_title_template.render(context) body = mail_body_template.render(context) try: email_from = getattr(settings, 'DEFAULT_FROM_EMAIL') except AttributeError: raise ImproperlyConfigured( 'DEFAULT_FROM_EMAIL setting needed for sending e-mails') mail.send_mail(title, body, email_from, recipients) if language is not None: translation.deactivate() email_logger.info(u"Email (%s) sent to %s\nTitle: %s\n%s\n\n" % (language, recipients, title, body))
def export_zone_view(request, origin): Domain = cache.get_model('powerdns_manager', 'Domain') obj = Domain.objects.get(name=origin) obj_display = force_unicode(obj) # Check zone ownership. if request.user != obj.created_by: messages.error(request, 'Permission denied for domain: %s' % obj_display) # Redirect to the Domain changelist. return render_to_response('powerdns_manager/zone/import/error.html', {}) return HttpResponseRedirect( reverse('admin:powerdns_manager_domain_changelist')) else: info_dict = { 'zone_text': generate_zone_file(origin), 'origin': origin, } return render_to_response('powerdns_manager/zone/export/zonefile.html', info_dict, context_instance=RequestContext(request))
def set_minimum_ttl(self, new_minimum_ttl): """Sets the minimum TTL. Accepts ``new_minimum_ttl`` (integer) SOA content: primary hostmaster serial refresh retry expire default_ttl Important --------- Make sure this is not called while the SOA record object is modified. TODO: Investigate whether it is needed to perform any checks against settings.PDNS_DEFAULT_RR_TTL """ Record = cache.get_model('powerdns_manager', 'Record') try: soa_rr = Record.objects.get(domain=self, type='SOA') except Record.DoesNotExist: raise Exception('SOA Resource Record does not exist.') else: bits = soa_rr.content.split() bits[6] = new_minimum_ttl soa_rr.content = ' '.join(bits) soa_rr.save()
def reset_api_key(modeladmin, request, queryset): if not modeladmin.has_change_permission(request): raise PermissionDenied DynamicZone = cache.get_model('powerdns_manager', 'DynamicZone') n = queryset.count() for domain_obj in queryset: # Only one DynamicZone instance for each Domain try: dz = DynamicZone.objects.get(domain=domain_obj) except DynamicZone.DoesNotExist: messages.error(request, 'Zone is not dynamic: %s' % domain_obj.name) n = n - 1 else: if dz.api_key: dz.api_key = generate_api_key() dz.save() else: messages.error(request, 'Zone is not dynamic: %s' % domain_obj.name) n = n - 1 if n: messages.info(request, 'Successfully reset the API key of %d domains.' % n)
def pdnssec_hash_zone_record(zone_name, record_name): """Generates hash for ordername field in NSEC3 non-narrow mode. ***** Special kudos to the folks at the #powerdns IRC channel for the help, especially Maik for pointing me to dns.name.Name.to_digestable() method. ***** Notes from RFCs for easy reference ---------------------------------- NSEC3PARAM Presentation Format: http://tools.ietf.org/html/rfc5155#section-4.3 4.3. Presentation Format The presentation format of the RDATA portion is as follows: - The Hash Algorithm field is represented as an unsigned decimal integer. The value has a maximum of 255. - The Flags field is represented as an unsigned decimal integer. The value has a maximum value of 255. - The Iterations field is represented as an unsigned decimal integer. The value is between 0 and 65535, inclusive. - The Salt Length field is not represented. - The Salt field is represented as a sequence of case-insensitive hexadecimal digits. Whitespace is not allowed within the sequence. This field is represented as "-" (without the quotes) when the Salt Length field is zero. 5. Calculation of the Hash http://tools.ietf.org/html/rfc5155#section-5 The hash calculation uses three of the NSEC3 RDATA fields: Hash Algorithm, Salt, and Iterations. Define H(x) to be the hash of x using the Hash Algorithm selected by the NSEC3 RR, k to be the number of Iterations, and || to indicate concatenation. Then define: IH(salt, x, 0) = H(x || salt), and IH(salt, x, k) = H(IH(salt, x, k-1) || salt), if k > 0 Then the calculated hash of an owner name is IH(salt, owner name, iterations), where the owner name is in the canonical form, defined as: The wire format of the owner name where: 1. The owner name is fully expanded (no DNS name compression) and fully qualified; 2. All uppercase US-ASCII letters are replaced by the corresponding lowercase US-ASCII letters; 3. If the owner name is a wildcard name, the owner name is in its original unexpanded form, including the "*" label (no wildcard substitution); This form is as defined in Section 6.2 of [RFC4034]. The method to calculate the Hash is based on [RFC2898]. "DNSSEC NSEC3 Hash Algorithms". The initial contents of this registry are: 0 is Reserved. 1 is SHA-1. 2-255 Available for assignment. """ Domain = cache.get_model('powerdns_manager', 'Domain') DomainMetadata = cache.get_model('powerdns_manager', 'DomainMetadata') the_domain = Domain.objects.get(name__exact=zone_name) nsec3param = DomainMetadata.objects.get(domain=the_domain, kind='NSEC3PARAM') algo, flags, iterations, salt = nsec3param.content.split() # dns.name.NAME expects an absolute name (with trailing dot) record_name = '%s.' % record_name.rstrip('.') record_name = Name(record_name.split('.')) # Prepare salt salt = salt.decode('hex') hashed_name = sha1hash(record_name.to_digestable(), salt) i = 0 while i < int(iterations): hashed_name = sha1hash(hashed_name, salt) i += 1 # Do standard base32 encoding final_data = base64.b32encode(hashed_name) # Apply the translation table to convert to base32hex encoding. final_data = final_data.translate(b32_to_ext_hex) # Return lower case representation as required by PowerDNS return final_data.lower()
def clone_zone(modeladmin, request, queryset): """Actions that clones the selected zone. Accepts only one selected zone. Clones: - Resource Records - Dynamic setting - Domain Metadata This action first displays a page which provides an input box to enter the origin of the new zone. It checks if the user has add & change permissions. It checks if a zone with the name that has been entered as new exists in the database. Based on: https://github.com/django/django/blob/1.4.2/django/contrib/admin/actions.py Important --------- In order to work requires some special form fields (see the template). """ opts = modeladmin.model._meta app_label = opts.app_label Domain = cache.get_model('powerdns_manager', 'Domain') Record = cache.get_model('powerdns_manager', 'Record') DynamicZone = cache.get_model('powerdns_manager', 'DynamicZone') DomainMetadata = cache.get_model('powerdns_manager', 'DomainMetadata') # Check the number of selected zones. This action can work on a single zone. n = queryset.count() if n != 1: messages.error(request, 'Only one zone may be selected for cloning.') return None # Check permissions perm_domain_add = '%s.%s' % (opts.app_label, opts.get_add_permission()) perm_domain_change = '%s.%s' % (opts.app_label, opts.get_change_permission()) perm_record_add = '%s.add_record' % opts.app_label perm_record_change = '%s.change_record' % opts.app_label if not request.user.has_perms([ perm_domain_add, perm_domain_change, perm_record_add, perm_record_change ]): raise PermissionDenied # Check that the user has change permission for the add and change modeladmin forms if not modeladmin.has_add_permission(request): raise PermissionDenied if not modeladmin.has_change_permission(request): raise PermissionDenied # The user has set a domain name for the clone through the forms.ClonedZoneDomainForm form. #if request.method == 'POST': if request.POST.get('post'): form = ClonedZoneDomainForm(request.POST) if form.is_valid(): # Store Data from the form # Store the new domain name for the clone. clone_domain_name = form.cleaned_data['clone_domain_name'] if not clone_domain_name: return None # Should never happen option_clone_dynamic = form.cleaned_data['option_clone_dynamic'] option_clone_metadata = form.cleaned_data['option_clone_metadata'] # Clone base zone # At this point queryset contain exactly one object. Checked above. domain_obj = queryset[0] # Create the clone (Check for uniqueness takes place in forms.ClonedZoneDomainForm clone_obj = Domain.objects.create( name=clone_domain_name, master=domain_obj.master, #last_check = domain_obj.last_check, type=domain_obj.type, #notified_serial = domain_obj.notified_serial, account=domain_obj.account, created_by=request. user # We deliberately do not use the domain_obj.created_by ) modeladmin.log_addition(request, clone_obj) # Clone Resource Records # Find all resource records of this domain domain_rr_qs = Record.objects.filter(domain=domain_obj) # Create the clone's RRs for rr in domain_rr_qs: # Construct RR name with interchanged domain clone_rr_name = interchange_domain(rr.name, domain_obj.name, clone_domain_name) # Special treatment to the content of SOA and SRV RRs if rr.type == 'SOA': content_parts = rr.content.split() # primary content_parts[0] = interchange_domain( content_parts[0], domain_obj.name, clone_domain_name) # hostmaster content_parts[1] = interchange_domain( content_parts[1], domain_obj.name, clone_domain_name) # Serial. Set new serial content_parts[2] = generate_serial() clone_rr_content = ' '.join(content_parts) elif rr.type == 'SRV': content_parts = rr.content.split() # target content_parts[2] = interchange_domain( content_parts[2], domain_obj.name, clone_domain_name) clone_rr_content = ' '.join(content_parts) else: clone_rr_content = interchange_domain( rr.content, domain_obj.name, clone_domain_name) # Create and save the cloned record. clone_rr = Record(domain=clone_obj, name=clone_rr_name, type=rr.type, content=clone_rr_content, ttl=rr.ttl, prio=rr.prio, auth=rr.auth, ordername=rr.ordername) clone_rr.save() #modeladmin.log_addition(request, clone_rr) # Clone Dynamic Zone setting if option_clone_dynamic: # Get the base domain's dynamic zone. # There is only one Dynamic Zone object for each zone. domain_dynzone_obj = DynamicZone.objects.get(domain=domain_obj) # Create and save the dynamic zone object for the clone. clone_dynzone_obj = DynamicZone( domain=clone_obj, is_dynamic=domain_dynzone_obj.is_dynamic) clone_dynzone_obj.save() # Clone the zone's metadata if option_clone_metadata: # Get the base domain's metadata object. # There is only one metadata object for each zone. domain_metadata_obj = DomainMetadata.objects.get( domain=domain_obj) # Create and save the metadata object for the clone. clone_metadata_obj = DomainMetadata( domain=clone_obj, kind=domain_metadata_obj.kind, content=domain_metadata_obj.content) clone_metadata_obj.save() messages.info(request, 'Successfully cloned %s zone to %s' % \ (domain_obj.name, clone_domain_name)) # Redirect to the new zone's change form. return HttpResponseRedirect( reverse('admin:%s_domain_change' % app_label, args=(clone_obj.id, ))) else: form = ClonedZoneDomainForm() info_dict = { 'form': form, 'queryset': queryset, 'opts': opts, 'app_label': app_label, 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, } return render_to_response('powerdns_manager/actions/clone_zone.html', info_dict, context_instance=RequestContext(request))
def generate_zone_file(origin): """Generates a zone file. Accepts the zone origin as string (no trailing dot). Returns the contents of a zone file that contains all the resource records associated with the domain with the provided origin. """ Domain = cache.get_model('powerdns_manager', 'Domain') Record = cache.get_model('powerdns_manager', 'Record') the_domain = Domain.objects.get(name__exact=origin) the_rrs = Record.objects.filter(domain=the_domain).order_by('-type') # Generate the zone file origin = Name((origin.rstrip('.') + '.').split('.')) # Create an empty dns.zone object. # We set check_origin=False because the zone contains no records. zone = dns.zone.from_text('', origin=origin, relativize=False, check_origin=False) rdclass = dns.rdataclass._by_text.get('IN') for rr in the_rrs: # Add trailing dot to rr.name record_name = rr.name.rstrip('.') + '.' if rr.type == 'SOA': # Add SOA Resource Record # SOA content: primary hostmaster serial refresh retry expire default_ttl bits = rr.content.split() # Primary nameserver of SOA record primary = bits[0].rstrip('.') + '.' mname = Name(primary.split('.')) # Responsible hostmaster from SOA record hostmaster = bits[1].rstrip('.') + '.' rname = Name(hostmaster.split('.')) rdtype = dns.rdatatype._by_text.get('SOA') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.SOA.SOA(rdclass, rdtype, mname = mname, rname = rname, serial = int(bits[2]), refresh = int(bits[3]), retry = int(bits[4]), expire = int(bits[5]), minimum = int(bits[6]) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'NS': # Add NS Resource Record rdtype = dns.rdatatype._by_text.get('NS') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.NS.NS(rdclass, rdtype, target = Name((rr.content.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'MX': # Add MX Resource Record rdtype = dns.rdatatype._by_text.get('MX') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.MX.MX(rdclass, rdtype, preference = int(rr.prio), exchange = Name((rr.content.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'TXT': # Add TXT Resource Record rdtype = dns.rdatatype._by_text.get('TXT') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.TXT.TXT(rdclass, rdtype, strings = rr.content.split(';') ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'CNAME': # Add CNAME Resource Record rdtype = dns.rdatatype._by_text.get('CNAME') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.CNAME.CNAME(rdclass, rdtype, target = Name((rr.content.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'A': # Add A Resource Record rdtype = dns.rdatatype._by_text.get('A') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.IN.A.A(rdclass, rdtype, address = rr.content ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'AAAA': # Add AAAA Resource Record rdtype = dns.rdatatype._by_text.get('AAAA') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.IN.AAAA.AAAA(rdclass, rdtype, address = rr.content ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'SPF': # Add SPF Resource Record rdtype = dns.rdatatype._by_text.get('SPF') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.SPF.SPF(rdclass, rdtype, strings = rr.content.split(';') ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'PTR': # Add PTR Resource Record rdtype = dns.rdatatype._by_text.get('PTR') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.PTR.PTR(rdclass, rdtype, target = Name((rr.content.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'SRV': # Add SRV Resource Record # weight port target weight, port, target = rr.content.split() rdtype = dns.rdatatype._by_text.get('SRV') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.IN.SRV.SRV(rdclass, rdtype, priority = int(rr.prio), weight = int(weight), port = int(port), target = Name((target.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) # Export text (from the source code of http://www.dnspython.org/docs/1.10.0/html/dns.zone.Zone-class.html#to_file) EOL = '\r\n' f = StringIO.StringIO() f.write('$ORIGIN %s%s' % (origin, EOL)) zone.to_file(f, sorted=True, relativize=False, nl=EOL) data = f.getvalue() f.close() return data
def rectify_zone(origin): """Fix up DNSSEC fields (order, auth). ***** Special kudos to the folks at the #powerdns IRC channel for the help, especially Habbie and Maik. ***** rectify_zone() accepts a string containing the zone origin. Returns nothing. PowerDNS Documentation at Chapter 12 Section 8.5: http://doc.powerdns.com/dnssec-modes.html#dnssec-direct-database If you ever need to read this code and you are not much into DNS stuff, here is some information about the involved DNS Resource Records and the terminology used. * A: http://www.simpledns.com/help/v52/index.html?rec_a.htm * AAAA: http://www.simpledns.com/help/v52/index.html?rec_aaaa.htm * DS: http://www.simpledns.com/help/v52/index.html?rec_ds.htm * NS: http://www.simpledns.com/help/v52/index.html?rec_ns.htm Terminology: * Glue: Glue records are needed to prevent circular references. Circular references exist where the name servers for a domain can't be resolved without resolving the domain they're responsible for. For example, if the name servers for yourdomain.com are ns1.yourdomain.com and ns2.yourdomain.com, the DNS client would not be able to get to either name server without knowing where yourdomain.com is. But this information is held by those name servers! A glue record is a hint that is provided by the parent DNS server. More at: http://www.webdnstools.com/dnstools/articles/glue_records * Delegation: When the authoritative name server for a domain receives a request for a subdomain's records and responds with NS records for other name servers, that is DNS delegation. AUTH Field ========== The 'auth' field should be set to '1' for data for which the zone itself is authoritative, which includes the SOA record and its own NS records. The 'auth' field should be 0 however for NS records which are used for delegation, and also for any glue (A, AAAA) records present for this purpose. Do note that the DS record for a secure delegation should be authoritative! ~~~~ PowerDNS Documentation at Chapter 12 Section 8.5 Rules used in the following code -------------------------------- 1. A & AAAA records (glue) of delegated names always get auth=0 2. DS records (used for secure delegation) get auth=1 3. Delegating NS records get auth=0 ORDERNAME Field =============== The 'ordername' field needs to be filled out depending on the NSEC/NSEC3 mode. When running in NSEC3 'Narrow' mode, the ordername field is ignored and best left empty. In NSEC mode, the ordername field should be NULL for any glue but filled in for delegation NS records and all authoritative records. In NSEC3 opt-out mode (the only NSEC3 mode PowerDNS currently supports), any non-authoritative records (as described for the 'auth' field) should have ordername set to NULL. In 'NSEC' mode, it should contain the relative part of a domain name, in reverse order, with dots replaced by spaces. So 'www.uk.powerdnssec.org' in the 'powerdnssec.org' zone should have 'uk www' as its ordername. In 'NSEC3' non-narrow mode, the ordername should contain a lowercase base32hex encoded representation of the salted & iterated hash of the full record name. pdnssec hash-zone-record zone record can be used to calculate this hash. ~~~~ PowerDNS Documentation at Chapter 12 Section 8.5 Rules used in the following code -------------------------------- If no crypto keys are present for the domain, DNSSEC is not enabled, so the ``ordername`` field is not necessary to be filled. However, this function always fills the ``ordername`` field regardless of the existence of crypto keys in the cryptokeys table. ``pdnssec rectify-zone ...`` fills the ordername field regardless of the existence of keys in cryptokeys, so we stick to that functionality. Modes are distinguished by the following criteria: (a) domain has no DNSSEC: there are no keys in cryptokeys (b) domain has DNSSEC with NSEC: there are keys in cryptokeys, and nothing about NSEC3 in domainmetadata (c) domain has DNSSEC with narrow NSEC3: cryptokeys+NSEC3PARAM+NSEC3NARROW (d) domain has DNSSEC with non-narrow NSEC3: cryptokeys+NSEC3PARAM (e) domain has DNSSEC with opt-out NSEC3: cryptokeys+NSEC3PARAM Note: non-narrow and opt-out NSEC3 modes cannot be distinguished. All rules mentioned in the documentation for these two modes apply if there are cryptokeys+NSEC3PARAM. Note 2: there is never a case in which ordername is filled in on glue record. 1) ordername in NSEC mode: - NULL for glue (A, AAAA) records - Filled for: a) delegation NS records b) all authoritative records (auth=1) It should contain the relative part of a domain name, in reverse order, with dots replaced by spaces. 2) ordername in NSEC3 'Narrow' mode: - empty (but not NULL) 3) ordername in NSEC3 'Opt-out' or 'Non-Narrow' modes: - NULL for non-authoritative records (auth=0) - lowercase base32hex encoded representation of the salted & iterated hash of the full record name for authoritative records (auth=1) EMPTY NON-TERMINALS =================== TODO: implement empty terminal support In addition, from 3.2 and up, PowerDNS fully supports empty non-terminals. If you have a zone example.com, and a host a.b.c.example.com in it, rectify-zone (and the AXFR client code) will insert b.c.example.com and c.example.com in the records table with type NULL (SQL NULL, not 'NULL'). Having these entries provides several benefits. We no longer reply NXDOMAIN for these shorter names (this was an RFC violation but not one that caused trouble). But more importantly, to do NSEC3 correctly, we need to be able to prove existence of these shorter names. The type=NULL records entry gives us a place to store the NSEC3 hash of these names. If your frontend does not add empty non-terminal names to records, you will get DNSSEC replies of 3.1-quality, which has served many people well, but we suggest you update your code as soon as possible! ~~~~ PowerDNS Documentation at Chapter 12 Section 8.5 """ Domain = cache.get_model('powerdns_manager', 'Domain') Record = cache.get_model('powerdns_manager', 'Record') DomainMetadata = cache.get_model('powerdns_manager', 'DomainMetadata') CryptoKey = cache.get_model('powerdns_manager', 'CryptoKey') # List containing domain parts origin_parts = origin.split('.') # Get the Domain instance that corresponds to the supplied origin # TODO: Do some exception handling here in case domain does not exist the_domain = Domain.objects.get(name=origin) # Get a list of the zone's records zone_rr_list = Record.objects.filter(domain__name=origin) # Find delegated names by checking the names of all NS and DS records. delegated_names_list = [] for rr in zone_rr_list: if rr.type not in ('NS', 'DS'): continue rr_name_parts = rr.name.split('.') if len(rr_name_parts) > len(origin_parts): # name is delegated if rr.name not in delegated_names_list: delegated_names_list.append(rr.name) # AUTH field management # Set auth=1 on all records initially for rr in zone_rr_list: rr.auth = True for delegated_name in delegated_names_list: # Set auth=0 to A & AAAA records (glue) of delegated names for rr in zone_rr_list: if rr.name == delegated_name and rr.type in ('A', 'AAAA'): rr.auth = False # DS records should already have auth=1 # Set auth=0 to NS records for rr in zone_rr_list: if rr.name == delegated_name and rr.type == 'NS': rr.auth = False # ORDERNAME field management # If no crypto keys are present for the domain, DNSSEC is not enabled, # so the ``ordername`` field is not necessary to be filled. However, the # following code always fills the ``ordername`` field. qs = CryptoKey.objects.filter(domain=the_domain) if not len(qs): # This is not a DNSSEC-enabled domain. # We still fill the ordername field as mentioned in the docstring. pass # Decide NSEC mode: try: nsec3 = DomainMetadata.objects.get( domain=the_domain, kind__startswith='NSEC3') except DomainMetadata.DoesNotExist: # NSEC Mode for rr in zone_rr_list: # Generate ordername content name_parts = rr.name.split('.') ordername_content_parts = name_parts[:-3] ordername_content_parts.reverse() ordername_content = ' '.join(ordername_content_parts) if rr.name in delegated_names_list: # Set ordername=NULL for A & AAAA records of delegated names (glue) if rr.type in ('A', 'AAAA'): rr.ordername = None # Fill ordername for: Delegation NS records elif rr.type == 'NS': rr.ordername = ordername_content # Fill ordername for: All auth=1 records if rr.auth: rr.ordername = ordername_content else: # NSEC3 Mode try: nsec3narrow = DomainMetadata.objects.get( domain=the_domain, kind='NSEC3NARROW') except DomainMetadata.DoesNotExist: # NSEC3 'Non-Narrow', 'Opt-out' mode for rr in zone_rr_list: if rr.auth: rr.ordername = pdnssec_hash_zone_record(rr.domain.name , rr.name) else: rr.ordername = None else: # NSEC3 'Narrow' Mode for rr in zone_rr_list: rr.ordername='' # Save the records # Since this is an internal maintenance function, the serial of the zone # is not updated. for rr in zone_rr_list: rr.save()
def toggle(request, app, model, id): model = cache.get_model(app, model) obj = model.objects.get(pk=id) return _toggle(request.user, obj)
def process_and_import_zone_data(zone, owner, overwrite=False): """ zone: dns.zone ***** Special kudos to Grig Gheorghiu for demonstrating how to manage zone files using dnspython in the following article: http://agiletesting.blogspot.com/2005/08/managing-dns-zone-files-with-dnspython.html ***** """ Domain = cache.get_model('powerdns_manager', 'Domain') Record = cache.get_model('powerdns_manager', 'Record') # Check if zone already exists in the database. try: domain_instance = Domain.objects.get(name=str(zone.origin).rstrip('.')) except Domain.DoesNotExist: pass # proceed with importing the new zone data else: # Zone exists if overwrite: # If ``overwrite`` has been checked, then delete the current zone. domain_instance.delete() else: raise Exception('Zone already exists. Consider using the "overwrite" option') # Import the new zone data to the database. # Create a domain instance #the_domain = Domain.objects.create(name=str(zone.origin).rstrip('.'), type='NATIVE', master='') the_domain = Domain( name=str(zone.origin).rstrip('.'), type='NATIVE', master='') if owner is not None: the_domain.created_by = owner the_domain.save() # Create RRs for name, node in zone.nodes.items(): rdatasets = node.rdatasets for rdataset in rdatasets: # Check instance variables of types: # http://www.dnspython.org/docs/1.10.0/html/dns.rdtypes-module.html for rdata in rdataset: rr = Record( domain=the_domain, name=str(name).rstrip('.'), # name is the dnspython node name change_date=int(time.time()), ttl = rdataset.ttl ) if rdataset.rdtype == dns.rdatatype._by_text['SOA']: # Set type rr.type = 'SOA' # Construct content rr.content = '%s %s %s %s %s %s %s' % ( str(rdata.mname).rstrip('.'), str(rdata.rname).rstrip('.'), rdata.serial, rdata.refresh, rdata.retry, rdata.expire, rdata.minimum ) elif rdataset.rdtype == dns.rdatatype._by_text['NS']: # http://www.dnspython.org/docs/1.10.0/html/dns.rdtypes.ANY.NS.NS-class.html rr.type = 'NS' rr.content = str(rdata.target).rstrip('.') elif rdataset.rdtype == dns.rdatatype._by_text['MX']: # http://www.dnspython.org/docs/1.10.0/html/dns.rdtypes.ANY.MX.MX-class.html rr.type = 'MX' rr.content = str(rdata.exchange).rstrip('.') rr.prio = rdata.preference elif rdataset.rdtype == dns.rdatatype._by_text['TXT']: # http://www.dnspython.org/docs/1.10.0/html/dns.rdtypes.ANY.TXT.TXT-class.html rr.type = 'TXT' rr.content = ' '.join(rdata.strings) elif rdataset.rdtype == dns.rdatatype._by_text['CNAME']: # http://www.dnspython.org/docs/1.10.0/html/dns.rdtypes.ANY.CNAME.CNAME-class.html rr.type = 'CNAME' rr.content = str(rdata.target).rstrip('.') elif rdataset.rdtype == dns.rdatatype._by_text['A']: # http://www.dnspython.org/docs/1.10.0/html/dns.rdtypes.IN.A.A-class.html rr.type = 'A' rr.content = rdata.address elif rdataset.rdtype == dns.rdatatype._by_text['AAAA']: # http://www.dnspython.org/docs/1.10.0/html/dns.rdtypes.IN.AAAA.AAAA-class.html rr.type = 'AAAA' rr.content = rdata.address elif rdataset.rdtype == dns.rdatatype._by_text['SPF']: # http://www.dnspython.org/docs/1.10.0/html/dns.rdtypes.ANY.SPF.SPF-class.html rr.type = 'SPF' rr.content = ' '.join(rdata.strings) elif rdataset.rdtype == dns.rdatatype._by_text['PTR']: # http://www.dnspython.org/docs/1.10.0/html/dns.rdtypes.ANY.PTR.PTR-class.html rr.type = 'PTR' rr.content = str(rdata.target).rstrip('.') elif rdataset.rdtype == dns.rdatatype._by_text['SRV']: # http://www.dnspython.org/docs/1.10.0/html/dns.rdtypes.IN.SRV.SRV-class.html rr.type = 'SRV' rr.content = '%d %d %s' % (rdata.weight, rdata.port, str(rdata.target).rstrip('.')) rr.save() # Update zone serial the_domain.update_serial() # Rectify zone rectify_zone(the_domain.name)
class Meta: model = cache.get_model('powerdns_manager', 'Domain')
def decode_model(model_str): return model_cache.get_model(*model_str.split(','))
class CryptoKeyInline(admin.TabularInline): model = cache.get_model('powerdns_manager', 'CryptoKey') fields = ('flags', 'active', 'content') extra = 0 verbose_name_plural = 'Crypto Keys'
class Meta: model = cache.get_model('powerdns_manager', 'Record')
class DomainMetadataInline(admin.TabularInline): model = cache.get_model('powerdns_manager', 'DomainMetadata') fields = ('kind', 'content', ) extra = 0 verbose_name_plural = 'Domain Metadata'
rectify_zone() would be called multiple times and only the last call would be the effective one. rectify_zone() must be called after all the records and the domain have been saved to the database. Here we execute the parent save_related() and then we call rectify zone through a custom signal. """ super(DomainAdmin, self).save_related(request, form, formsets, change) # Send the zone_saved signal zone_saved.send(sender=self.model, instance=form.instance) admin.site.register(cache.get_model('powerdns_manager', 'Domain'), DomainAdmin) class TsigKeyAdmin(admin.ModelAdmin): fields = ('name', 'algorithm', 'secret', 'date_modified') readonly_fields = ('date_modified', ) list_display = ('name', 'algorithm', 'date_modified') list_filter = ('algorithm', ) search_fields = ('name', ) verbose_name = 'TSIG Key' verbose_name_plural = 'TSIG Keys' def queryset(self, request): qs = super(TsigKeyAdmin, self).queryset(request) if not request.user.is_superuser: # Non-superusers see the records they have created
def set_ttl_bulk(modeladmin, request, queryset): """Actions that resets TTL information on all resource records of the zone to the specified value. This action first displays a page which provides an input box to enter the new TTL. It checks if the user has change permission. Based on: https://github.com/django/django/blob/1.4.2/django/contrib/admin/actions.py Important --------- In order to work requires some special form fields (see the template). """ opts = modeladmin.model._meta app_label = opts.app_label Domain = cache.get_model('powerdns_manager', 'Domain') Record = cache.get_model('powerdns_manager', 'Record') perm_domain_change = '%s.%s' % (opts.app_label, opts.get_change_permission()) perm_record_change = '%s.change_record' % opts.app_label if not request.user.has_perms([perm_domain_change, perm_record_change]): raise PermissionDenied # Check that the user has change permission for the Re model if not modeladmin.has_change_permission(request): raise PermissionDenied # The user has set a new TTL value through the forms.TtlSelectionForm form. # Make the changes to the selected objects and return a None to display the # change list view again. #if request.method == 'POST': if request.POST.get('post'): form = TtlSelectionForm(request.POST) if form.is_valid(): new_ttl = form.cleaned_data['new_ttl'] reset_zone_minimum = form.cleaned_data['reset_zone_minimum'] n = queryset.count() record_count = 0 if n and new_ttl: for domain_obj in queryset: # Find all resource records of this domain qs = Record.objects.filter(domain=domain_obj) # Now set the new TTL for rr in qs: rr.ttl = int(new_ttl) # If this is the SOA record and ``reset_zone_minimum`` has # been checked, set the minimum TTL of the SOA record equal # to the ``new_ttl`` value # # Important: We do not call ``models.Domain.set_minimum_ttl()`` # because we edit the SOA record here. # if reset_zone_minimum and rr.type == 'SOA': bits = rr.content.split() # SOA content: primary hostmaster serial refresh retry expire default_ttl bits[6] = str(new_ttl) rr.content = ' '.join(bits) # Save the resource record rr.save() rr_display = force_unicode(rr) modeladmin.log_change(request, rr, rr_display) # Update the domain serial domain_obj.update_serial() record_count += len(qs) messages.info( request, 'Successfully updated %d zones (%d total records).' % (n, record_count)) # Return None to display the change list page again. return None else: form = TtlSelectionForm() info_dict = { 'form': form, 'queryset': queryset, 'opts': opts, 'app_label': app_label, 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, } return render_to_response('powerdns_manager/actions/set_ttl.html', info_dict, context_instance=RequestContext(request))
# See template ``powerdns_manager/domain_change_form.html`` if "_transfer" in request.POST: return HttpResponseRedirect(reverse('zone_transfer', args=(obj.id,))) elif "_clone" in request.POST: return HttpResponseRedirect(reverse('zone_clone', args=(obj.id,))) elif "_set_rr_ttl" in request.POST: return HttpResponseRedirect(reverse('zone_set_ttl', args=(obj.id,))) elif "_reset_api_key" in request.POST: Domain = cache.get_model('powerdns_manager', 'Domain') qs = Domain.objects.filter(id=obj.id) reset_api_key(self, request, qs) return super(DomainAdmin, self).response_change(request, obj) admin.site.register(cache.get_model('powerdns_manager', 'Domain'), DomainAdmin) class TsigKeyAdmin(admin.ModelAdmin): fields = ('name', 'algorithm', 'secret', 'date_modified') readonly_fields = ('date_modified', ) list_display = ('name', 'algorithm', 'date_modified') list_filter = ('algorithm', ) search_fields = ('name', ) verbose_name = 'TSIG Key' verbose_name_plural = 'TSIG Keys' def queryset(self, request): qs = super(TsigKeyAdmin, self).queryset(request) if not request.user.is_superuser:
def __get_model(self, name): """Helper method to get Model for given name e.g. network.interfaces -> Interfaces """ app, model = name.split('.', 1) return cache.get_model(app, model)