| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511 |
- ##############################################
- # $Id: 96_allowed.pm 13219 2017-01-24 13:43:25Z rudolfkoenig $
- package main;
- use strict;
- use warnings;
- use vars qw(@FW_httpheader); # HTTP header, line by line
- #####################################
- sub
- allowed_Initialize($)
- {
- my ($hash) = @_;
- $hash->{DefFn} = "allowed_Define";
- $hash->{AuthorizeFn} = "allowed_Authorize";
- $hash->{AuthenticateFn} = "allowed_Authenticate";
- $hash->{AttrFn} = "allowed_Attr";
- $hash->{AttrList} = "disable:0,1 validFor allowedCommands allowedDevices ".
- "basicAuth basicAuthMsg password globalpassword ".
- "basicAuthExpiry";
- $hash->{UndefFn} = "allowed_Undef";
- }
- #####################################
- sub
- allowed_Define($$)
- {
- my ($hash, $def) = @_;
- my @l = split(" ", $def);
- if(@l > 2) {
- my %list;
- for(my $i=2; $i<@l; $i++) {
- $list{$l[$i]} = 1;
- }
- $hash->{devices} = \%list;
- }
- $auth_refresh = 1;
- readingsSingleUpdate($hash, "state", "active", 0);
- return undef;
- }
- sub
- allowed_Undef($$)
- {
- $auth_refresh = 1;
- return undef;
- }
- #####################################
- # Return 0 for don't care, 1 for Allowed, 2 for forbidden.
- sub
- allowed_Authorize($$$$)
- {
- my ($me, $cl, $type, $arg) = @_;
- return 0 if($me->{disabled});
- if( $cl->{SNAME} ) {
- return 0 if(!$me->{validFor} || $me->{validFor} !~ m/\b$cl->{SNAME}\b/);
- } else {
- return 0 if(!$me->{validFor} || $me->{validFor} !~ m/\b$cl->{NAME}\b/);
- }
- if($type eq "cmd") {
- return 0 if(!$me->{allowedCommands});
- # Return 0: allow stacking with other instances, see Forum#46380
- return ($me->{allowedCommands} =~ m/\b$arg\b/) ? 0 : 2;
- }
- if($type eq "devicename") {
- return 0 if(!$me->{allowedDevices});
- return ($me->{allowedDevices} =~ m/\b$arg\b/) ? 0 : 2;
- }
- return 0;
- }
- #####################################
- # Return 0 for authentication not needed, 1 for auth-ok, 2 for wrong password
- sub
- allowed_Authenticate($$$$)
- {
- my ($me, $cl, $param) = @_;
- return 0 if($me->{disabled});
- return 0 if(!$me->{validFor} || $me->{validFor} !~ m/\b$cl->{SNAME}\b/);
- my $aName = $me->{NAME};
- if($cl->{TYPE} eq "FHEMWEB") {
- my $basicAuth = AttrVal($aName, "basicAuth", undef);
- delete $cl->{".httpAuthHeader"};
- return 0 if(!$basicAuth);
- my $FW_httpheader = $param;
- my $secret = $FW_httpheader->{Authorization};
- $secret =~ s/^Basic //i if($secret);
-
- # Check for Cookie in headers if no basicAuth header is set
- my $authcookie;
- if (!$secret && $FW_httpheader->{Cookie}) {
- if(AttrVal($aName, "basicAuthExpiry", 0)) {
- my $cookie = "; ".$FW_httpheader->{Cookie}.";";
- $authcookie = $1 if ( $cookie =~ /; AuthToken=([^;]+);/ );
- $secret = $authcookie;
- }
- }
-
- my $pwok = ($secret && $secret eq $basicAuth); # Base64
- my ($user, $password);
- if($secret && $basicAuth =~ m/^{.*}$/) {
- eval "use MIME::Base64";
- if($@) {
- Log3 $aName, 1, $@;
- } else {
- ($user, $password) = split(":", decode_base64($secret));
- $pwok = eval $basicAuth;
- Log3 $aName, 1, "basicAuth expression: $@" if($@);
- }
- }
- # Add Cookie header ONLY if authentication with basicAuth was succesful
- if($pwok && (!defined($authcookie) || $secret ne $authcookie)) {
- my $time = AttrVal($aName, "basicAuthExpiry", 0);
- if ( $time ) {
- $time = int($time*86400+time());
- # generate timestamp according to RFC-1130 in Expires
- my $expires = FmtDateTimeRFC1123($time);
- readingsBeginUpdate($me);
- readingsBulkUpdate($me,'lastAuthUser', $user, 1);
- readingsBulkUpdate($me,'lastAuthExpires', $time, 1);
- readingsBulkUpdate($me,'lastAuthExpiresFmt', $expires, 1);
- readingsEndUpdate($me, 1);
- # set header with expiry
- $cl->{".httpAuthHeader"} = "Set-Cookie: AuthToken=".$secret.
- "; Path=/ ; Expires=$expires\r\n" ;
- }
- }
- return 1 if($pwok);
- my $msg = AttrVal($aName, "basicAuthMsg", "FHEM: login required");
- $cl->{".httpAuthHeader"} = "HTTP/1.1 401 Authorization Required\r\n".
- "WWW-Authenticate: Basic realm=\"$msg\"\r\n";
- return 2;
- }
- if($cl->{TYPE} eq "telnet") {
- my $pw = AttrVal($aName, "password", undef);
- if(!$pw) {
- $pw = AttrVal($aName, "globalpassword", undef);
- $pw = undef if($pw && $cl->{NAME} =~ m/_127.0.0.1_/);
- }
- return 0 if(!$pw);
- return 2 if(!defined($param));
- if($pw =~ m/^{.*}$/) {
- my $password = $param;
- my $ret = eval $pw;
- Log3 $aName, 1, "password expression: $@" if($@);
- return ($ret ? 1 : 2);
- }
- return ($pw eq $param) ? 1 : 2;
- }
- return 0;
- }
- sub
- allowed_Attr(@)
- {
- my ($type, $devName, $attrName, @param) = @_;
- my $hash = $defs{$devName};
- my $set = ($type eq "del" ? 0 : (!defined($param[0]) || $param[0]) ? 1 : 0);
- if($attrName eq "disable") {
- readingsSingleUpdate($hash, "state", $set ? "disabled" : "active", 1);
- if($set) {
- $hash->{disabled} = 1;
- } else {
- delete($hash->{disabled});
- }
- } elsif($attrName eq "allowedCommands" || # hoping for some speedup
- $attrName eq "allowedDevices" ||
- $attrName eq "validFor") {
- if($set) {
- $hash->{$attrName} = join(" ", @param);
- } else {
- delete($hash->{$attrName});
- }
- } elsif(($attrName eq "basicAuth" ||
- $attrName eq "password" || $attrName eq "globalpassword") &&
- $type eq "set") {
- foreach my $d (devspec2array("TYPE=(FHEMWEB|telnet)")) {
- delete $defs{$d}{Authenticated} if($defs{$d});
- }
- }
- return undef;
- }
- 1;
- =pod
- =item helper
- =item summary authorize command execution based on frontend
- =item summary_DE authorisiert Befehlsausführung basierend auf dem Frontend
- =begin html
- <a name="allowed"></a>
- <h3>allowed</h3>
- <ul>
- <br>
- <a name="alloweddefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> allowed <deviceList></code>
- <br><br>
- Authorize execution of commands and modification of devices based on the
- frontend used and/or authenticate users.<br><br>
- If there are multiple instances defined which are valid for a given
- frontend device, then all authorizations must succeed. For authentication
- it is sufficient when one of the instances succeeds. The checks are
- executed in alphabetical order of the allowed instance names.<br><br>
- <b>Note:</b> this module should work as intended, but no guarantee
- can be given that there is no way to circumvent it.<br><br>
- Examples:
- <ul><code>
- define allowedWEB allowed<br>
- attr allowedWEB validFor WEB,WEBphone,WEBtablet<br>
- attr allowedWEB basicAuth { "$user:$password" eq "admin:secret" }<br>
- attr allowedWEB allowedCommands set,get<br><br>
- define allowedTelnet allowed<br>
- attr allowedTelnet validFor telnetPort<br>
- attr allowedTelnet password secret<br>
- </code></ul>
- <br>
- </ul>
- <a name="allowedset"></a>
- <b>Set:</b> <ul>N/A</ul><br>
- <a name="allowedget"></a>
- <b>Get</b> <ul>N/A</ul><br>
- <a name="allowedattr"></a>
- <b>Attributes</b>
- <ul>
- <li><a href="#disable">disable</a></li><br>
- <a name="allowedCommands"></a>
- <li>allowedCommands<br>
- A comma separated list of commands allowed from the matching frontend
- (see validFor).<br>
- If set to an empty list <code>, (i.e. comma only)</code>
- then no comands are allowed. If set to <code>get,set</code>, then only
- a "regular" usage is allowed via set and get, but changing any
- configuration is forbidden.<br>
- </li><br>
- <a name="allowedDevices"></a>
- <li>allowedDevices<br>
- A comma separated list of device names which can be manipulated via the
- matching frontend (see validFor).
- </li><br>
- <a name="basicAuth"></a>
- <li>basicAuth, basicAuthMsg<br>
- request a username/password authentication for FHEMWEB access. You have
- to set the basicAuth attribute to the Base64 encoded value of
- <user>:<password>, e.g.:<ul>
- # Calculate first the encoded string with the commandline program<br>
- $ echo -n fhemuser:secret | base64<br>
- ZmhlbXVzZXI6c2VjcmV0<br>
- # Set the FHEM attribute<br>
- attr allowed_WEB basicAuth ZmhlbXVzZXI6c2VjcmV0
- </ul>
- You can of course use other means of base64 encoding, e.g. online
- Base64 encoders.<br>
- If the argument of basicAuth is enclosed in { }, then it will be
- evaluated, and the $user and $password variable will be set to the
- values entered. If the return value is true, then the password will be
- accepted.<br>
- If basicAuthMsg is set, it will be displayed in the
- popup window when requesting the username/password.<br>
- Example:<br>
- <ul><code>
- attr allowedWEB basicAuth { "$user:$password" eq "admin:secret" }<br>
- </code></ul>
- </li><br>
- <a name="basicAuthExpiry"></a>
- <li>basicAuthExpiry<br>
- allow the basicAuth to be kept valid for a given number of days.
- So username/password as specified in basicAuth are only requested
- after a certain period.
- This is achieved by sending a cookie to the browser that will expire
- after the given period.
- Only valid if basicAuth is set.
- </li><br>
- <a name="password"></a>
- <li>password<br>
- Specify a password for telnet instances, which has to be entered as the
- very first string after the connection is established. If the argument
- is enclosed in {}, then it will be evaluated, and the $password
- variable will be set to the password entered. If the return value is
- true, then the password will be accepted. If this parameter is
- specified, FHEM sends telnet IAC requests to supress echo while
- entering the password. Also all returned lines are terminated with
- \r\n.
- Example:<br>
- <ul>
- <code>
- attr allowed_tPort password secret<br>
- attr allowed_tPort password {"$password" eq "secret"}
- </code>
- </ul>
- Note: if this attribute is set, you have to specify a password as the
- first argument when using fhem.pl in client mode:
- <ul>
- perl fhem.pl localhost:7072 secret "set lamp on"
- </ul>
- </li><br>
- <a name="globalpassword"></a>
- <li>globalpassword<br>
- Just like the attribute password, but a password will only required for
- non-local connections.
- </li><br>
- <a name="validFor"></a>
- <li>validFor<br>
- A comma separated list of frontend names. Currently supported frontends
- are all devices connected through the FHEM TCP/IP library, e.g. telnet
- and FHEMWEB. <b>Note: changed behaviour:</b>The allowed instance is
- only active, if this attribute is set.
- </li>
- </ul>
- <br>
- </ul>
- =end html
- =begin html_DE
- <a name="allowed"></a>
- <h3>allowed</h3>
- <ul>
- <br>
- <a name="alloweddefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> allowed <deviceList></code>
- <br><br>
- Authorisiert das Ausführen von Kommandos oder das Ändern von
- Geräten abhängig vom verwendeten Frontend.<br>
- Falls man mehrere allowed Instanzen definiert hat, die für dasselbe
- Frontend verantwortlich sind, dann müssen alle Authorisierungen
- genehmigt sein, um das Befehl ausführen zu können. Auf der
- anderen Seite reicht es, wenn einer der Authentifizierungen positiv
- entschieden wird. Die Prüfungen werden in alphabetischer Reihenfolge
- der Instanznamen ausgeführt. <br><br>
- <b>Achtung:</b> das Modul sollte wie hier beschrieben funktionieren,
- allerdings können wir keine Garantie geben, daß man sie nicht
- überlisten, und Schaden anrichten kann.<br><br>
- Beispiele:
- <ul><code>
- define allowedWEB allowed<br>
- attr allowedWEB validFor WEB,WEBphone,WEBtablet<br>
- attr allowedWEB basicAuth { "$user:$password" eq "admin:secret" }<br>
- attr allowedWEB allowedCommands set,get<br><br>
- define allowedTelnet allowed<br>
- attr allowedTelnet validFor telnetPort<br>
- attr allowedTelnet password secret<br>
- </code></ul>
- <br>
- </ul>
- <a name="allowedset"></a>
- <b>Set:</b> <ul>N/A</ul><br>
- <a name="allowedget"></a>
- <b>Get</b> <ul>N/A</ul><br>
- <a name="allowedattr"></a>
- <b>Attribute</b>
- <ul>
- <li><a href="#disable">disable</a>
- </li><br>
- <a name="allowedCommands"></a>
- <li>allowedCommands<br>
- Eine Komma getrennte Liste der erlaubten Befehle des passenden
- Frontends (siehe validFor). Bei einer leeren Liste (, dh. nur ein
- Komma) wird dieser Frontend "read-only".
- Falls es auf <code>get,set</code> gesetzt ist, dann sind in dieser
- Frontend keine Konfigurationsänderungen möglich, nur
- "normale" Bedienung der Schalter/etc.
- </li><br>
- <a name="allowedDevices"></a>
- <li>allowedDevices<br>
- Komma getrennte Liste von Gerätenamen, die mit dem passenden
- Frontend (siehe validFor) geändert werden können.
- </li><br>
- <a name="basicAuth"></a>
- <li>basicAuth, basicAuthMsg<br>
- Betrifft nur FHEMWEB Instanzen (siehe validFor): Fragt username /
- password zur Autentifizierung ab. Es gibt mehrere Varianten:
- <ul>
- <li>falls das Argument <b>nicht</b> in { } eingeschlossen ist, dann wird
- es als base64 kodiertes benutzername:passwort interpretiert.
- Um sowas zu erzeugen kann man entweder einen der zahlreichen
- Webdienste verwenden, oder das base64 Programm. Beispiel:
- <ul><code>
- $ echo -n fhemuser:secret | base64<br>
- ZmhlbXVzZXI6c2VjcmV0<br>
- fhem.cfg:<br>
- attr WEB basicAuth ZmhlbXVzZXI6c2VjcmV0
- </code></ul>
- </li>
- <li>Werden die Argumente in { } angegeben, wird es als perl-Ausdruck
- ausgewertet, die Variablen $user and $password werden auf die
- eingegebenen Werte gesetzt. Falls der Rückgabewert wahr ist,
- wird die Anmeldung akzeptiert.
- Beispiel:<br>
- <ul><code>
- attr allwedWEB basicAuth { "$user:$password" eq "admin:secret" }<br>
- </code></ul>
- </li>
- </ul>
- </li><br>
- <a name="password"></a>
- <li>password<br>
- Betrifft nur telnet Instanzen (siehe validFor): Bezeichnet ein
- Passwort, welches als allererster String eingegeben werden muss,
- nachdem die Verbindung aufgebaut wurde. Wenn das Argument in { }
- eingebettet ist, dann wird es als Perl-Ausdruck ausgewertet, und die
- Variable $password mit dem eingegebenen Passwort verglichen. Ist der
- zurückgegebene Wert wahr (true), wird das Passwort akzeptiert.
- Falls dieser Parameter gesetzt wird, sendet FHEM telnet IAC Requests,
- um ein Echo während der Passworteingabe zu unterdrücken.
- Ebenso werden alle zurückgegebenen Zeilen mit \r\n abgeschlossen.
- Beispiel:<br>
- <ul>
- <code>
- attr allowed_tPort password secret<br>
- attr allowed_tPort password {"$password" eq "secret"}
- </code>
- </ul>
- Hinweis: Falls dieses Attribut gesetzt wird, muss als erstes Argument
- ein Passwort angegeben werden, wenn fhem.pl im Client-mode betrieben
- wird:
- <ul>
- <code>
- perl fhem.pl localhost:7072 secret "set lamp on"
- </code>
- </ul>
- </li><br>
- <a name="globalpassword"></a>
- <li>globalpassword<br>
- Betrifft nur telnet Instanzen (siehe validFor): Entspricht dem
- Attribut password; ein Passwort wird aber ausschließlich für
- nicht-lokale Verbindungen verlangt.
- </li><br>
- <a name="validFor"></a>
- <li>validFor<br>
- Komma separierte Liste von Frontend-Instanznamen. Aktuell werden nur
- Frontends unterstützt, die das FHEM TCP/IP Bibliothek verwenden,
- z.Bsp. telnet und FHEMWEB. <b>Achtung, Änderung:</b> falls nicht
- gesetzt, ist die allowed Instanz nicht aktiv.
- </li>
- </ul>
- <br>
- </ul>
- =end html_DE
- =cut
|