############################################## # $Id$ package main; use strict; use warnings; ##################################### 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}); return 0 if(!$me->{validFor} || $me->{validFor} !~ m/\b$cl->{SNAME}\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 if($secret && $basicAuth =~ m/^{.*}$/) { eval "use MIME::Base64"; if($@) { Log3 $aName, 1, $@; } else { my ($user, $password) = split(":", decode_base64($secret)); $pwok = eval $basicAuth; Log3 $aName, 1, "basicAuth expression: $@" if($@); } } # Add Cookie header ONLY if # authentication with basic auth was succesful # (meaning if no or wrong authcookie set) if ( ( $pwok ) && ( ( ! defined($authcookie) ) || ( $secret ne $authcookie ) ) ) { # no cookie set but authorization succesful # check if cookie should be set --> Cookie Attribute != 0 my $time = int(AttrVal($aName, "basicAuthExpiry", 0)); if ( $time ) { # time specified in days until next expiry (but timestamp is in seconds) $time *= 86400; $time += time(); # generate timestamp according to RFC-1130 in Expires my $expires = "Expires=".FmtDateTimeRFC1123($time); # set header with expiry $cl->{".httpAuthHeader"} = "Set-Cookie: AuthToken=".$secret. "; Path=/ ; ".$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->{disable} = 1; } else { delete($hash->{disable}); } } 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 =begin html
define <name> allowed <deviceList>
define allowedWEB allowed
attr allowedWEB validFor WEB,WEBphone,WEBtablet
attr allowedWEB basicAuth { "$user:$password" eq "admin:secret" }
attr allowedWEB allowedCommands set,get
define allowedTelnet allowed
attr allowedTelnet validFor telnetPort
attr allowedTelnet password secret
, (i.e. comma only)
then no comands are allowed. If set to get,set
, then only
a "regular" usage is allowed via set and get, but changing any
configuration is forbidden.
attr allowedWEB basicAuth { "$user:$password" eq "admin:secret" }
attr allowed_tPort password secret
attr allowed_tPort password {"$password" eq "secret"}
define <name> allowed <deviceList>
define allowedWEB allowed
attr allowedWEB validFor WEB,WEBphone,WEBtablet
attr allowedWEB basicAuth { "$user:$password" eq "admin:secret" }
attr allowedWEB allowedCommands set,get
define allowedTelnet allowed
attr allowedTelnet validFor telnetPort
attr allowedTelnet password secret
get,set
gesetzt ist, dann sind in dieser
Frontend keine Konfigurationsänderungen möglich, nur
"normale" Bedienung der Schalter/etc.
$ echo -n fhemuser:secret | base64
ZmhlbXVzZXI6c2VjcmV0
fhem.cfg:
attr WEB basicAuth ZmhlbXVzZXI6c2VjcmV0
attr allwedWEB basicAuth { "$user:$password" eq "admin:secret" }
attr allowed_tPort password secret
attr allowed_tPort password {"$password" eq "secret"}
perl fhem.pl localhost:7072 secret "set lamp on"