diff --git a/server/api/flukso/priv/dispatch.conf b/server/api/flukso/priv/dispatch.conf index c56046c..4a73fc9 100644 --- a/server/api/flukso/priv/dispatch.conf +++ b/server/api/flukso/priv/dispatch.conf @@ -1 +1,2 @@ +{["sensor"], flukso_sensor, []}. {["sensor", sensor], flukso_sensor_xyz, []}. diff --git a/server/api/flukso/src/flukso.app b/server/api/flukso/src/flukso.app index 0fe0969..166c852 100644 --- a/server/api/flukso/src/flukso.app +++ b/server/api/flukso/src/flukso.app @@ -6,6 +6,7 @@ flukso_app, flukso_sup, flukso_deps, + flukso_sensor, flukso_sensor_xyz ]}, {registered, []}, diff --git a/server/api/flukso/src/flukso.erl b/server/api/flukso/src/flukso.erl index 10ffaf6..59a9cb3 100644 --- a/server/api/flukso/src/flukso.erl +++ b/server/api/flukso/src/flukso.erl @@ -1,10 +1,24 @@ -%% @author author -%% @copyright YYYY author. - -%% @doc TEMPLATE. +%% @author Bart Van Der Meerssche +%% @copyright (C) 2009-2011 Bart Van Der Meerssche +%%% +%%% This program is free software: you can redistribute it and/or modify +%%% it under the terms of the GNU General Public License as published by +%%% the Free Software Foundation, either version 3 of the License, or +%%% (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%%% GNU General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program. If not, see . +%%% +%% @doc Flukso module spec -module(flukso). --author('author '). +-author('Bart Van Der Meerssche '). + -export([start/0, start_link/0, stop/0]). ensure_started(App) -> @@ -16,7 +30,8 @@ ensure_started(App) -> end. mysql_prepare() -> - mysql:prepare(permissions, <<"SELECT permissions FROM logger_tokens WHERE meter = ? AND token = ?">>). + mysql:prepare(permissions, <<"SELECT permissions FROM logger_tokens WHERE meter = ? AND token = ?">>), + mysql:prepare(device_key, <<"SELECT sha FROM logger_devices WHERE device = ?">>). %% @spec start_link() -> {ok,Pid::pid()} %% @doc Starts the app for inclusion in a supervisor tree diff --git a/server/api/flukso/src/flukso.hrl b/server/api/flukso/src/flukso.hrl index 8b13789..98f94b1 100644 --- a/server/api/flukso/src/flukso.hrl +++ b/server/api/flukso/src/flukso.hrl @@ -1 +1,159 @@ +%% @author Bart Van Der Meerssche +%% @copyright (C) 2009-2011 Bart Van Der Meerssche +%%% +%%% This program is free software: you can redistribute it and/or modify +%%% it under the terms of the GNU General Public License as published by +%%% the Free Software Foundation, either version 3 of the License, or +%%% (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%%% GNU General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program. If not, see . +%%% +%% @doc Common record definitions and helper functions for the Flukso API. +-define(MINUTE, 60). +-define(QUARTER, 900). +-define(HOUR, 3600). +-define(DAY, 86400). +-define(WEEK, 604800). +-define(MONTH, 2419200). +-define(YEAR, 31536000). + +-record(state, + {rrdSensor, + rrdStart, + rrdEnd, + rrdResolution, + rrdFactor, + token, + jsonpCallback}). + +%% checks +check_version(Version) -> + case Version of + "1.0" -> {Version, true}; + _ -> {false, false} + end. + +check_version(undefined, undefined) -> + {false, false}; +check_version(Version, undefined) -> + check_version(Version); +check_version(undefined, Version) -> + check_version(Version); +check_version(_, _) -> + {false, false}. + +check_sensor(Sensor) -> + check_32hex(Sensor). + +check_32hex(String) -> + case re:run(String, "[0-9a-f]+", []) of + {match, [{0,32}]} -> {String, true}; + _ -> {false, false} + end. + +check_time(undefined, undefined, _End, _Resolution) -> + {false, false, false, false}; +check_time(Interval, undefined, undefined, undefined) -> + case default_resolution(Interval) of + false -> {false, false, false, false}; + DefResolution -> check_time(Interval, undefined, undefined, DefResolution) + end; +check_time(Interval, undefined, undefined, Resolution) -> + Now = unix_time(), + case {time_to_seconds(Interval), time_to_seconds(Resolution)} of + {false, _} -> {false, false, false, false}; + {_, false} -> {false, false, false, false}; + {IntervalSec, ResolutionSec} -> + AlignedEnd = time_align(Now, ResolutionSec), + AlignedStart = AlignedEnd - IntervalSec, + {integer_to_list(AlignedStart), integer_to_list(AlignedEnd), integer_to_list(ResolutionSec), true} + end; +check_time(undefined, Start, undefined, Resolution) -> + check_time(undefined, Start, integer_to_list(unix_time()), Resolution); +check_time(undefined, Start, End, undefined) -> + check_time(undefined, Start, End, "minute"); +check_time(undefined, Start, End, Resolution) -> + case {re:run(Start, "[0-9]+", []), re:run(End, "[0-9]+", []), time_to_seconds(Resolution)} of + {_, _, false} -> {false, false, false, false}; + {{match, [{0,_}]}, {match, [{0,_}]}, ResolutionSec} -> + AlignedStart = time_align(list_to_integer(Start), ResolutionSec), + AlignedEnd = time_align(list_to_integer(End), ResolutionSec), + {integer_to_list(AlignedStart), integer_to_list(AlignedEnd), integer_to_list(ResolutionSec), true}; + _ -> {false, false, false, false} + end; +check_time(_, _, _, _) -> + {false, false, false, false}. + +check_unit(Unit) -> + Units = [{"watt", 3600}, + {"kwhperyear", 31536}, + {"eurperyear", 5676}, + {"audperyear", 5991}], + + case lists:keyfind(Unit, 1, Units) of + false -> {false, false}; + {_Unit, RrdFactor} -> {RrdFactor, true} + end. + +check_token(undefined, undefined) -> + {false, false}; +check_token(Token, undefined) -> + check_32hex(Token); +check_token(undefined, Token) -> + check_32hex(Token); +check_token(_, _) -> + {false, false}. + +check_jsonp_callback(undefined) -> + {undefined, true}; +check_jsonp_callback(JsonpCallback) -> + Length = string:len(JsonpCallback), + + case re:run(JsonpCallback, "[0-9a-zA-Z_]+", []) of + {match, [{0, Length}]} -> {JsonpCallback, true}; + _ -> {false, false} + end. + +%% helper functions +unix_time() -> + {Megaseconds, Seconds, _Microseconds} = erlang:now(), + Megaseconds*1000000 + Seconds. + +time_align(Time, Resolution) -> + (Time div Resolution) * Resolution. + +default_resolution(Interval) -> + DefResolutions = [{"15min", "minute"}, + {"hour", "minute"}, + {"day", "15min"}, + {"week", "day"}, + {"month", "day"}, + {"year", "week"}, + {"night", "day"}], + + case lists:keyfind(Interval, 1, DefResolutions) of + false -> false; + {_Interval, Defresolution} -> Defresolution + end. + +time_to_seconds(Time) -> + Times = [{"minute", ?MINUTE}, + {"15min", ?QUARTER}, + {"hour", ?HOUR}, + {"day", ?DAY}, + {"week", ?WEEK}, + {"month", ?MONTH}, + {"year", ?YEAR}, + {"night", ?MONTH}], + + case lists:keyfind(Time, 1, Times) of + false -> false; + {_Time, TimeSec} -> TimeSec + end. diff --git a/server/api/flukso/src/flukso_app.erl b/server/api/flukso/src/flukso_app.erl index ef7928d..37d59c4 100644 --- a/server/api/flukso/src/flukso_app.erl +++ b/server/api/flukso/src/flukso_app.erl @@ -1,10 +1,23 @@ -%% @author author -%% @copyright YYYY author. - +%% @author Bart Van Der Meerssche +%% @copyright (C) 2009-2011 Bart Van Der Meerssche +%%% +%%% This program is free software: you can redistribute it and/or modify +%%% it under the terms of the GNU General Public License as published by +%%% the Free Software Foundation, either version 3 of the License, or +%%% (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%%% GNU General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program. If not, see . +%%% %% @doc Callbacks for the flukso application. -module(flukso_app). --author('author '). +-author('Bart Van Der Meerssche '). -behaviour(application). -export([start/2,stop/1]). diff --git a/server/api/flukso/src/flukso_deps.erl b/server/api/flukso/src/flukso_deps.erl index 7467e49..65ba78b 100644 --- a/server/api/flukso/src/flukso_deps.erl +++ b/server/api/flukso/src/flukso_deps.erl @@ -1,12 +1,25 @@ -%% @author author -%% @copyright YYYY author. - +%% @author Bart Van Der Meerssche +%% @copyright (C) 2009-2011 Bart Van Der Meerssche +%%% +%%% This program is free software: you can redistribute it and/or modify +%%% it under the terms of the GNU General Public License as published by +%%% the Free Software Foundation, either version 3 of the License, or +%%% (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%%% GNU General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program. If not, see . +%%% %% @doc Ensure that the relatively-installed dependencies are on the code %% loading path, and locate resources relative %% to this application's path. -module(flukso_deps). --author('author '). +-author('Bart Van Der Meerssche '). -export([ensure/0, ensure/1]). -export([get_base_dir/0, get_base_dir/1]). diff --git a/server/api/flukso/src/flukso_sensor.erl b/server/api/flukso/src/flukso_sensor.erl new file mode 100644 index 0000000..df72e4e --- /dev/null +++ b/server/api/flukso/src/flukso_sensor.erl @@ -0,0 +1,91 @@ +%% @author Bart Van Der Meerssche +%% @copyright (C) 2009-2011 Bart Van Der Meerssche +%%% +%%% This program is free software: you can redistribute it and/or modify +%%% it under the terms of the GNU General Public License as published by +%%% the Free Software Foundation, either version 3 of the License, or +%%% (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%%% GNU General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program. If not, see . +%%% +%% @doc Flukso API: /sensor resource specification + +-module(flukso_sensor). +-author('Bart Van Der Meerssche '). + +-export([init/1, + allowed_methods/2, + malformed_request/2, + is_authorized/2, + process_post/2]). + +-include_lib("webmachine/include/webmachine.hrl"). +-include("flukso.hrl"). + +init([]) -> + {ok, undefined}. + +% debugging +%init(Config) -> +% {{trace, "/tmp"}, Config}. + +allowed_methods(ReqData, State) -> + {['POST'], ReqData, State}. + +malformed_request(ReqData, State) -> + {_Version, ValidVersion} = check_version(wrq:get_req_header("X-Version", ReqData)), + {_Device, ValidDevice} = check_32hex(wrq:get_req_header("X-Device", ReqData)), + {_Digest, ValidDigest} = check_32hex(wrq:get_req_header("X-Digest", ReqData)), + + {case {ValidVersion, ValidDevice, ValidDigest} of + {true, true, true} -> false; + _ -> true + end, + ReqData, State}. + +is_authorized(ReqData, State) -> + {data, Result} = mysql:execute(pool, device_key, [wrq:get_req_header("X-Device", ReqData)]), + [[Key]] = mysql:get_result_rows(Result), + Data = wrq:req_body(ReqData), + <> = crypto:sha_mac(Key, Data), + Digest = list_to_binary(io_lib:format("~40.16.0b", [X])), + + {case list_to_binary(wrq:get_req_header("X-Digest", ReqData)) of + Digest -> true; + _WrongDigest -> "access refused" + end, + ReqData, State}. + +% JSON: {"measurements":{"":[[,],...,[,]], +% ..., +% "":[[,],...,[,]]}} +% +% Mochijson2: {struct,[{<<"measurements">>, {struct, [{<<"">>, [[,],...,[,]]}, +% ..., +% {<<"">>, [[,],...,[,]]}]}}]} +% +process_post(ReqData, State) -> + {struct, JsonData} = mochijson2:decode(wrq:req_body(ReqData)), + {struct, Measurements} = proplists:get_value(<<"measurements">>, JsonData), + Ids = proplists:get_keys(Measurements), + RrdResponse = [update_rrd(RrdSensor, proplists:get_value(RrdSensor, Measurements)) || RrdSensor <- Ids], + + JsonResponse = mochijson2:encode({struct, [{<<"response">>, {struct, RrdResponse}}]}), + {true , wrq:set_resp_body(JsonResponse, ReqData), State}. + +update_rrd(RrdSensor, TimeSeries) -> + Path = "var/data/base/", + RrdData = [[integer_to_list(Time), ":", integer_to_list(Counter), " "] || [Time, Counter] <- TimeSeries], + +%debugging: io:format("~s~n", [[Path, [binary_to_list(RrdSensor)|".rrd"], " ", RrdData]]), + + case erlrrd:update([Path, [binary_to_list(RrdSensor)|".rrd"], " ", RrdData]) of + {ok, _RrdResponse} -> {RrdSensor, <<"ok">>}; + {error, RrdResponse} -> {RrdSensor, list_to_binary(RrdResponse)} + end. diff --git a/server/api/flukso/src/flukso_sensor_xyz.erl b/server/api/flukso/src/flukso_sensor_xyz.erl index 229760d..00ce506 100644 --- a/server/api/flukso/src/flukso_sensor_xyz.erl +++ b/server/api/flukso/src/flukso_sensor_xyz.erl @@ -1,8 +1,24 @@ -%% @author icarus75 -%% @copyright 2009-2010 flukso.net -%% @doc Flukso webmachine_resource. +%% @author Bart Van Der Meerssche +%% @copyright (C) 2009-2011 Bart Van Der Meerssche +%%% +%%% This program is free software: you can redistribute it and/or modify +%%% it under the terms of the GNU General Public License as published by +%%% the Free Software Foundation, either version 3 of the License, or +%%% (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%%% GNU General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program. If not, see . +%%% +%% @doc Flukso API: /sensor/xyz resource specification -module(flukso_sensor_xyz). +-author('Bart Van Der Meerssche '). + -export([init/1, allowed_methods/2, malformed_request/2, @@ -12,15 +28,7 @@ process_post/2]). -include_lib("webmachine/include/webmachine.hrl"). - --record(state, - {rrdSensor, - rrdStart, - rrdEnd, - rrdResolution, - rrdFactor, - token, - jsonpCallback}). +-include("flukso.hrl"). init([]) -> {ok, undefined}. @@ -118,138 +126,4 @@ to_json(ReqData, #state{rrdSensor = RrdSensor, rrdStart = RrdStart, rrdEnd = Rrd end. process_post(ReqData, #state{rrdSensor = RrdSensor} = State) -> - Path = "var/data/base/", - - {struct, JsonData} = mochijson2:decode(wrq:req_body(ReqData)), - Measurements = proplists:get_value(<<"measurements">>, JsonData), - RrdData = [[integer_to_list(Time), ":", integer_to_list(Counter), " "] || [Time, Counter] <- Measurements], - -%debugging: io:format("~s~n", [[Path, [RrdSensor|".rrd"], " ", RrdData]]), - - case erlrrd:update([Path, [RrdSensor|".rrd"], " ", RrdData]) of - {ok, _RrdResponse} -> RrdResponse = "ok"; - {error, RrdResponse} -> true - end, - - JsonResponse = mochijson2:encode({struct, [{<<"response">>, list_to_binary(RrdResponse)}]}), - - {true , wrq:set_resp_body(JsonResponse, ReqData), State}. - -%% checks -check_version(undefined, undefined) -> - {false, false}; -check_version(Version, undefined) -> - case Version of - "1.0" -> {Version, true}; - _ -> {false, false} - end; -check_version(undefined, Version) -> - check_version(Version, undefined); -check_version(_, _) -> - {false, false}. - -check_sensor(Sensor) -> - case re:run(Sensor, "[0-9a-f]+", []) of - {match, [{0,32}]} -> {Sensor, true}; - _ -> {false, false} - end. - -check_time(undefined, undefined, _End, _Resolution) -> - {false, false, false, false}; -check_time(Interval, undefined, undefined, undefined) -> - case default_resolution(Interval) of - false -> {false, false, false, false}; - DefResolution -> check_time(Interval, undefined, undefined, DefResolution) - end; -check_time(Interval, undefined, undefined, Resolution) -> - Now = unix_time(), - case {time_to_seconds(Interval), time_to_seconds(Resolution)} of - {false, _} -> {false, false, false, false}; - {_, false} -> {false, false, false, false}; - {IntervalSec, ResolutionSec} -> - AlignedEnd = time_align(Now, ResolutionSec), - AlignedStart = AlignedEnd - IntervalSec, - {integer_to_list(AlignedStart), integer_to_list(AlignedEnd), integer_to_list(ResolutionSec), true} - end; -check_time(undefined, Start, undefined, Resolution) -> - check_time(undefined, Start, integer_to_list(unix_time()), Resolution); -check_time(undefined, Start, End, undefined) -> - check_time(undefined, Start, End, "minute"); -check_time(undefined, Start, End, Resolution) -> - case {re:run(Start, "[0-9]+", []), re:run(End, "[0-9]+", []), time_to_seconds(Resolution)} of - {_, _, false} -> {false, false, false, false}; - {{match, [{0,_}]}, {match, [{0,_}]}, ResolutionSec} -> - AlignedStart = time_align(list_to_integer(Start), ResolutionSec), - AlignedEnd = time_align(list_to_integer(End), ResolutionSec), - {integer_to_list(AlignedStart), integer_to_list(AlignedEnd), integer_to_list(ResolutionSec), true}; - _ -> {false, false, false, false} - end; -check_time(_, _, _, _) -> - {false, false, false, false}. - -check_unit(Unit) -> - Units = [{"watt", 3600}, - {"kwhperyear", 31536}, - {"eurperyear", 5676}, - {"audperyear", 5991}], - - case lists:keyfind(Unit, 1, Units) of - false -> {false, false}; - {_Unit, RrdFactor} -> {RrdFactor, true} - end. - -check_token(undefined, undefined) -> - {false, false}; -check_token(Token, undefined) -> - check_sensor(Token); -check_token(undefined, Token) -> - check_sensor(Token); -check_token(_, _) -> - {false, false}. - -check_jsonp_callback(undefined) -> - {undefined, true}; -check_jsonp_callback(JsonpCallback) -> - Length = string:len(JsonpCallback), - - case re:run(JsonpCallback, "[0-9a-zA-Z_]+", []) of - {match, [{0, Length}]} -> {JsonpCallback, true}; - _ -> {false, false} - end. - -%% helper functions -unix_time() -> - {Megaseconds, Seconds, _Microseconds} = erlang:now(), - Megaseconds*1000000 + Seconds. - -time_align(Time, Resolution) -> - (Time div Resolution) * Resolution. - -default_resolution(Interval) -> - DefResolutions = [{"15min", "minute"}, - {"hour", "minute"}, - {"day", "15min"}, - {"week", "day"}, - {"month", "day"}, - {"year", "week"}, - {"night", "day"}], - - case lists:keyfind(Interval, 1, DefResolutions) of - false -> false; - {_Interval, Defresolution} -> Defresolution - end. - -time_to_seconds(Time) -> - Times = [{"minute", 60}, - {"15min", 900}, - {"hour", 3600}, - {"day", 86400}, - {"week", 604800}, - {"month", 2419200}, - {"year", 31536000}, - {"night", 2419200}], - - case lists:keyfind(Time, 1, Times) of - false -> false; - {_Time, TimeSec} -> TimeSec - end. + {true , ReqData, State}. diff --git a/server/api/flukso/src/flukso_sup.erl b/server/api/flukso/src/flukso_sup.erl index 3713ac6..025047b 100644 --- a/server/api/flukso/src/flukso_sup.erl +++ b/server/api/flukso/src/flukso_sup.erl @@ -1,10 +1,23 @@ -%% @author author -%% @copyright YYYY author. - -%% @doc Supervisor for the flukso application. +%% @author Bart Van Der Meerssche +%% @copyright (C) 2009-2011 Bart Van Der Meerssche +%%% +%%% This program is free software: you can redistribute it and/or modify +%%% it under the terms of the GNU General Public License as published by +%%% the Free Software Foundation, either version 3 of the License, or +%%% (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +%%% GNU General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program. If not, see . +%%% +%% @doc Supervisor for the flukso application. -module(flukso_sup). --author('author '). +-author('Bart Van Der Meerssche '). -behaviour(supervisor). diff --git a/server/drupal/modules/logger/logger.module b/server/drupal/modules/logger/logger.module index 68b41c1..dac9d82 100644 --- a/server/drupal/modules/logger/logger.module +++ b/server/drupal/modules/logger/logger.module @@ -319,7 +319,7 @@ function _logger_dashboard($type, $function, $interval) { WHERE uid = %d AND type = '%s' AND function = '%s' ORDER BY created", $user->uid, $type, $function)); - if (in_array('pro', array_values($user->roles))) + if (is_array($user->roles) && in_array('pro', array_values($user->roles))) $result_me = db_query("SELECT meter, function FROM {logger_meters} WHERE uid = %d AND type = '%s' and chart = %d diff --git a/server/drupal/modules/logger/xmlrpc1.inc b/server/drupal/modules/logger/xmlrpc1.inc index 80296a9..4db9451 100644 --- a/server/drupal/modules/logger/xmlrpc1.inc +++ b/server/drupal/modules/logger/xmlrpc1.inc @@ -155,7 +155,7 @@ function _logger_authenticate_hmac_sha1($auth, $message) { return TRUE; } else { - watchdog_xmlrpc('logger.auth', 'HMAC-SHA1 authentication failed for device: %device', array('%device' => $auth['device']), WATCHDOG_ERROR); + // watchdog_xmlrpc('logger.auth', 'HMAC-SHA1 authentication failed for device: %device', array('%device' => $auth['device']), WATCHDOG_ERROR); return FALSE; } }