Mittwoch, 19. September 2012

Freeradius: Account gültig ab?

Da hat es mich doch glatt in die tiefen von FreeRadius 2 verschlagen. Aufgabenstellung war, ein Account erst zum Zeitpunkt X als gültig zu erklären. Klingt eigentlich recht simple, hat aber viele Nerven gekostet.
Ausgangssituation; ein komplett durch konfigurierter FreeRadius inkl. verwendeten SQL-Modul, welches über den "authorize_check_query" uns alle zu überprüfenden Kriterien ausgibt. so habe ich für mein vorhaben ein neues Attribute geschaffen: "Activate-Account-At" dies ist ein Unix-Timestamp und soll den Zeitpunkt beschreiben ab wann der Account erst gültig ist.
Zuerst müssen wir FreeRadius dieses neue Attribute bekannt machen und so tragen wir es im "dictionary" ein:

ATTRIBUTE    Activate-Account-At        3080    integer

nun weiß FreeRadius dass es sich hier um ein "Integer" handelt und kann entsprechend mit umgehen. nun müssen wir dieses Attribute nur noch auswerten. Leider gibt es kein Modul was dies für uns erledigen könnte und so bedienen wir uns einfach der im FreeRadius verwendete "un-lang". In der Section "authorize" prüfen wir nun ob unser Attribute überhaupt gesetzt wurde und vergleichen es mit der build-in Variable von FreeRadius "%l" welche den aktuellen Unix-Timestamp enspricht.

authorize {
  ...
    if("%l" && "%{check:Activate-Account-At}") {
        if("%l" < "%{check:Activate-Account-At}") {
            reject
        }
    }
  ...
}

wie zu sehen ist, verwenden wir bei unserem neuen Attribute einen Präfix, der sagt FreeRadius, dass er das gewünschte Attribute aus unseren "check"-Attributen nehmen soll.
Und schon ist unsere neue Regel fertig, war gar nicht mal so wild ;)
Ich habe an dieser Stelle erstmal aufgehört weiter zu basteln, was mir an dieser stelle besser gefallen würde, wenn anstelle eines "Unix-Timestamp" ein "DateTime"-Object verwendet werden könnte. FreeRadius selbst hat auch eine "date" interpretation für Attribute, leider habe ich noch nicht das passende gegenstück gefunden was mir dann per "un-lang" ein gültigen Vergleich liefern könnte. Wer hier eine gute Idee hat, darf hier gerne ein Kommentar hinterlassen.
Alternativ könnte man den vergleich per SQL machen, ist aber meiner meinung nach auch nicht die feine englische Art hier den DB-Server mit zu nerven.

Freitag, 31. August 2012

Zeitberechnung

Heute habe ich mal wieder Feuer gemacht! Ein kleiner Schnipsel Code welcher ermöglicht zB unter JavaScript über die angabe von Sekunden eine genaue Angabe zu machen was dieser Wert in Sekunden/Minuten/Stunden/Tage/Wochen/Monate/Jahre ist:

var elapsed = (((((ts) / 31536000) | 0) > 0) ? (((ts) / 31536000) | 0) + " Jahre " : "") +
((((((ts) / 2592000) % 12) | 0) > 0) ? ((((ts) / 2592000) % 12) | 0) + " Monate " : "") +
((((((ts) / 604800) % 4) | 0) > 0) ? ((((ts) / 604800) % 4) | 0) + " Wochen " : "") +
((((((ts) / 86400) % 7) | 0) > 0) ? ((((ts) / 86400) % 7) | 0) + " Tage " : "") +
((((((ts) / 3600) % 24) | 0) > 0) ? ((((ts) / 3600) % 24) | 0) + " Std " : "") +
((((((ts) / 60) % 60) | 0) > 0) ? ((((ts) / 60) % 60) | 0) + " Min " : "") +
((((((ts)) % 60) > 0) | 0) ? ((((ts)) % 60) | 0) + " Sek" : "");

dies lässt sicht natürlich auch auf jede Andere Sprache anwenden. Der Knackpunkt an diesem Schnipsel ist, dass es durch das Bitweise "ODER" mit "0" ein cast auf ein Integer gibt.

Dienstag, 31. Juli 2012

Bei Symfony 2.1 fehlen Doctrine-Settings

Ich wollte es kaum glauben, aber als ich nach meinem letzten Datenbankumbau im MySQL-Workbench nun mittels Symfony 2.1 CLI ein Datenbank reverse-Engeneering durchführen wollte flog mir Doctrine um die Ohren.

Hier der eigentlich recht simple Aufbau der Problemstellung, zwei Tabellen die jeweils mit zwei M:N Verknüpfungen verwurstet sind.



So, nun Symfony 2.1 mitteilen dass wir gerne daraus Entity-Klassen erstellt haben möchten:

php app/console doctrine:mapping:convert xml ./src/SkyDiablo/StoreBundle/Resources/config/doctrine/metadata/orm --from-database --force

Alles in allem nix besonderes bisher... nur was passiert jetzt. Doctrine wird nun aufgefordert aus der hinterlegten DB, Entitys zu erstellen, was es mit folgender Meldung und mit vollster überzeugung verweigert:

[Doctrine\ORM\Mapping\MappingException]
Property "radUser" in "RadAttribute" was already declared, but it must be declared only once

Was ist passiert? Bei der Erstellung des "RadAttribute"-Entity wollte Doctrine nun für jede M:N Verknüpfung ein Feld anlegen und da die Verknüpfungen auf "RadUser" zeigen, liegt es nahe auch das Feld so zu bennen. Leider haben wir hier zwei Verknüpfungen auf die gleiche Tabelle, was dazu führen würde, dass wir zwei mal das gleiche Feld hätten. Das erkennt Doctrine und streikt an der stelle. Leider bringt uns das kein Meter weiter, keinerlei Übergabeparameter oder Configs im "Doctrine-Bundle" ermöglichen uns hier abhilfe zu schaffen.
Da ich aber öffters über diesen Weg meine Entitys erstelle und ich somit darauf angeweisen bin ging für mich das große Suchen los. Ich habe etliche Zeilen Source debuggt und mich um den Verstand gegoogelt, nichts brauchbares gefunden. Doch da, nach langem hin und her konnte ich ein Schlupfloch finden... Doch erst nochmal etwas Hintergrundinformationen. Die eigentliche Klasse welche mir den Kopf zerbrochen hat, ist diese hier:

Doctrine\ORM\Mapping\Driver\DatabaseDriver

 In ihr werden über die function "loadMetadataForClass" die Feldnamen definiert. Dort habe ich nun gesucht wie ich meinem Problem abhilfe schaffen kann und konnte recht schnell feststellen dass die Feldnamen über die function "getFieldNameForColumn" generiert werden. Und hier gibt uns Doctrine nun auch endliche die Chance selbst Feldnamen zu vergeben! Überglücklich über meinem Fund war auch schon das nächste Problem da, wie sag ich nun Doctrine dass ich an dieser stelle gerne alternative Feldnamen verwenden möchte. Wie bereits gesagt, von seiten Symfony bzw dem Doctrine-Bundle habe ich keine Möglichkeit gefunden.
Nach weiterem langen Gesuche bin ich auf folgendes gestoßen, die Klasse "DatabaseDriver" wird bei Doctrine als eine sogenannte "Metadata-Driver-Implementierung" genutzt und über die Klasse "Doctrine\ORM\Configuration" verwaltet, also vorgehalten. Im Doctrine-Bundle wird das ganze in der Klasse "Doctrine\Bundle\DoctrineBundle\Command\ImportMappingDoctrineCommand" in der Function "execute" so zusammengesteckt:

$em = $this->getEntityManager($input->getOption('em'));

$databaseDriver = new DatabaseDriver($em->getConnection()->getSchemaManager());
$em->getConfiguration()->setMetadataDriverImpl($databaseDriver);

Und weiter keinerlei Möglichkeit auf den "$databaseDriver" zuzugreifen um eventuell alternative Feldnamen zu hinterlegen. Nun kommt der Clue an der geschichte, die Klasse "Doctrine\ORM\Configuration" wird im Doctrine-Bundle über eine Parameter in der "orm.xml" definiert:

<parameters>
        <parameter key="doctrine.orm.configuration.class">Doctrine\ORM\Configuration</parameter>

Hier  habe ich nun angesetzt und diesen Parameter überschrieben indem ich ein neuen gleichnamigen Parameter angelegt habe und somit diese Definition überschrieben habe und dann meine eigene Configuration-Class implementierung angeben habe. In jener habe ich nun die Function "setMetadataDriverImpl" überschrieben und schon hatte ich endlich zugriff auf die gewünschte Klasse und konnte nun so Doctrine mitteilen, dass ich gerne alternative Feldnamen nutzen möchte. Der einfachhalber habe ich alles hard-codiert hinterlegt, könnte man aber natürlich noch über eigene Konfigurations-möglichkeiten im eigenen Bundle durchaus erweitern. In der Praxis sieht das nun so aus:

namespace SkyDiablo\Doctrine\ORM\ConfigurationExtenderBundle\Controller;

use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;

class Configuration extends \Doctrine\ORM\Configuration {

    public function setMetadataDriverImpl(MappingDriver $driverImpl) {
        parent::setMetadataDriverImpl($driverImpl);

        if ($driverImpl instanceof \Doctrine\ORM\Mapping\Driver\DatabaseDriver) {
            /* @var $driverImpl \Doctrine\ORM\Mapping\Driver\DatabaseDriver */
            $driverImpl->setFieldNameForColumn("rad_user_has_rad_check_attribute", "rad_user_id", "radUserCheck");
            $driverImpl->setFieldNameForColumn("rad_user_has_rad_reply_attribute", "rad_user_id", "radUserReply");
            $driverImpl->setFieldNameForColumn("rad_user_has_rad_check_attribute", "rad_attribute_id", "radAttributeCheck");
            $driverImpl->setFieldNameForColumn("rad_user_has_rad_reply_attribute", "rad_attribute_id", "radAttributeReply");

        }
    }

}

Nun werden die Entitys problemlos erstellt und bekommen jeweils eigenständige Feldnamen! Sollte ich mit meinem Aufwand hier voll daneben liegen und es gibt doch schon eine Lösung mit Boardmitteln, bin ich über jeden Kommentar dankbar!

EDIT: 13.08.2012

Ein alternativer weg, ist über die generelle Namens-Convention von Doctrine zu arbeiten. Das geht zwar etwas hier am Ziel vorbei, lässt aber dennoch das geleiche ergebnis mit sich bringen. Eine entsprechende Doku ist hier zu finden:

http://developmentwithart.com/2012/07/04/solving-the-sql-naming-dilemma-by-using-doctrine2-naming-strategy/

EDIT: 18.06.2013

Habe eben beim Durchstöbern meiner Posts das hier noch gefunden, könnte eventuell auch nochmal abhilfe schaffen. Hatte aber bisher noch keine Zeit mich damit zu beschäftigen:

https://github.com/doctrine/doctrine2/pull/241


Greez,
  hoessi...