I have a project I'm working on that uses an API for it request, but in order to preform them I need to generate the token first.
Before the API was update everything was working, after the update I don't know how to adjust my code to make it work again.
This was the code that worked before the update (Android | Kotlin):
fun hmacHash(str: String, secret: String): String {
val sha256HMAC = Mac.getInstance("HmacSHA256")
val secretKey = SecretKeySpec(secret.toByteArray(), "HmacSHA256")
sha256HMAC.init(secretKey)
return convertToHex(sha256HMAC.doFinal(str.toByteArray()))
}
fun convertToHex(data: ByteArray): String {
val buf = StringBuilder()
for (b in data) {
var halfbyte = (b.toInt() shr 4) and (0x0F.toByte()).toInt()
var two_halfs = 0
do {
buf.append(if (halfbyte in 0..9) ('0'.toInt() + halfbyte).toChar() else ('a'.toInt() + (halfbyte - 10)).toChar())
halfbyte = (b and 0x0F).toInt()
} while (two_halfs++ < 1)
}
return buf.toString()
}
Which was equivalent to this PHP code:
hash_hmac('sha256', $string, $privateKey);
But now after the update the php code looks like this:
hash_hmac('sha256', $string, hex2bin($privateKey));
And I don't know how to adjust my code to make it work with this new change.
From what I can deduce, the PHP code made that change because $privateKey went from being plain text to being hex-encoded. So hex2bin was needed to change it back to plain text (hex2bin changes hex-encoded text to plain text; a confusingly named function if you ask me).
Since your secret is plain text, you don't need to change anything to match. But there are other ways to improve your code. For example, converting a byte array to a hex-encoded string is much easier than that.
fun hmacHash(str: String, secret: String): String {
val sha256HMAC = Mac.getInstance("HmacSHA256")
val bytes = secret.toByteArray()
val secretKey = SecretKeySpec(bytes, "HmacSHA256")
sha256HMAC.init(secretKey)
return convertToHex(sha256HMAC.doFinal(str.toByteArray()))
}
fun convertToHex(data: ByteArray): String =
data.joinToString("") { "%02x".format(it) }
In a previous Using R, how to reference variable variables (or variables variable) a la PHP[post]
I asked a question about something in R analagous to PHP $$ function:
Using R stats, I want to access a variable variable scenario similar to PHP double-dollar-sign technique: http://php.net/manual/en/language.variables.variable.php
Specifically, I am looking for a function in R that is equivalent to $$ in PHP.
The get( response works for strings (characters).
lapply is a way to loop over lists
Or I can loop over and get the values ...
for(name in names(vars))
{
val = vars[[name]];
I still haven't had the $$ function in R answered, although the lapply solved what I needed in the moment.
`$$` <- function
that allows any variable type to be evaluated. That is still the question.
UPDATES
> mlist = list('four'="score", 'seven'="years");
> str = 'mlist$four'
> mlist
$four
[1] "score"
$seven
[1] "years"
> str
[1] "mlist$four"
> get(str)
Error in get(str) : object 'mlist$four' not found
> mlist$four
[1] "score"
Or how about attributes for an object such as mobj#index
UPDATES #2
So let's put specific context on the need. I was hacking the texreg package to build a custom latex output of 24 models of regression for a research paper. I am using plm fixed effects, and the default output of texreg uses dcolumns to center, which I don't like (I prefer r#{}l, so I wanted to write my own template. The purpose for me, to code this, is for me to write extensible code that I can use again and again. I can rebuild my 24 tables across 4 pages in seconds, so if the data change, or if I want to tweak the function, I immediately have a nice answer. The power of abstraction.
As I hacked this, I wanted to get more than the number of observations, but also the number of groups, which can be any user defined index. In my case it is "country" (wait for it, hence, the need for variable variables).
If I do a lookup of the structure, what I want is right there: model$model#index$country which would be nice to simply call as $$('model$model#index$country'); where I can easily build the string using paste. Nope, this is my workaround.
getIndexCount = function(model,key="country")
{
myA = attr(summary(model)$model,"index");
for(i in 1:length(colnames(myA)))
{
if(colnames(myA)[i] == key) {idx = i; break;}
}
if(!is.na(idx))
{
length(unique(myA[,idx]));
} else {
FALSE;
}
}
UPDATES #3
Using R, on the command line, I can type in a string and it gets evaluated. Why can't that internal function be directly accessed, and the element captured that then gets printed to the screen?
There is no equivalent function in R. get() works for all types, not just strings.
Here is what I came up with, after chatting with the R-bug group, and getting some ideas from them. KUDOS!
`$$` <- function(str)
{
E = unlist( strsplit(as.character(str),"[#]") );
k = length(E);
if(k==1)
{
eval(parse(text=str));
} else {
# k = 2
nstr = paste("attributes(",E[1],")",sep="");
nstr = paste(nstr,'$',E[2],sep="");
if(k>2) {
for(i in 3:k)
{
nstr = paste("attributes(",nstr,")",sep="");
nstr = paste(nstr,'$',E[i],sep="");
}
}
`$$`(nstr);
}
}
Below are some example use cases, where I can directly access what the str(obj) is providing... Extending the utility of the '$' operator by also allowing '#' for attributes.
model = list("four" = "score", "seven"="years");
str = 'model$four';
result = `$$`(str);
print(result);
matrix = matrix(rnorm(1000), ncol=25);
str='matrix[1:5,8:10]';
result = `$$`(str);
print(result);
## Annette Dobson (1990) "An Introduction to Generalized Linear Models".
## Page 9: Plant Weight Data.
ctl <- c(4.17,5.58,5.18,6.11,4.50,4.61,5.17,4.53,5.33,5.14);
trt <- c(4.81,4.17,4.41,3.59,5.87,3.83,6.03,4.89,4.32,4.69);
group <- gl(2, 10, 20, labels = c("Ctl","Trt"));
weight <- c(ctl, trt);
lm.D9 <- lm(weight ~ group);
lm.D90 <- lm(weight ~ group - 1); # omitting intercept
myA = anova(lm.D9); myA; str(myA);
str = 'myA#heading';
result = `$$`(str);
print(result);
myS = summary(lm.D90); myS; str(myS);
str = 'myS$terms#factors';
result = `$$`(str);
print(result);
str = 'myS$terms#factors#dimnames';
result = `$$`(str);
print(result);
str = 'myS$terms#dataClasses#names';
result = `$$`(str);
print(result);
After realizing the back-tick can be a bit tedious, I chose to update the function, calling it access
access <- function(str)
{
E = unlist( strsplit(as.character(str),"[#]") );
k = length(E);
if(k==1)
{
eval(parse(text=str));
} else {
# k = 2
nstr = paste("attributes(",E[1],")",sep="");
nstr = paste(nstr,'$',E[2],sep="");
if(k>2) {
for(i in 3:k)
{
nstr = paste("attributes(",nstr,")",sep="");
nstr = paste(nstr,'$',E[i],sep="");
}
}
access(nstr);
}
}
I know this question has been asked before, but I've had little success even after visiting similar questions. I'm currently developing a Flash project which involves being able to create an account and log in. However, I keep getting "undefined" from my attempts to parse PHP output.
Here is my ActionScript 3.0 code for the function that contains the code.
function gosubmit(event:MouseEvent) {
if (username.text != "" && password.text != "") {
var phpVars:URLVariables = new URLVariables();
var phpFileRequest:URLRequest = new URLRequest();
phpFileRequest.url = "php/controlpanel.php";
phpFileRequest.method = URLRequestMethod.POST;
phpFileRequest.data = phpVars;
var phpLoader:URLLoader = new URLLoader();
phpLoader.dataFormat = URLLoaderDataFormat.VARIABLES;
phpLoader.addEventListener(Event.COMPLETE, execResult);
phpVars.username = username.text;
phpVars.password = password.text;
phpLoader.load(phpFileRequest);
phpLoader.addEventListener(Event.COMPLETE, execResult);
function execResult(event:Event) {
trace(event.target.data.execResult);
}
}
Here is the PHP code in php/controlpanel.php.
<?php
mysql_connect("localhost", "root", "password");
// change localhost to something else later
// change password to something else later
mysql_select_db("game");
$username = mysql_real_escape_string($_POST['username']);
$password = mysql_real_escape_string($_POST['password']);
$salt_mysql = mysql_query("SELECT salt FROM players WHERE username='".$username."'");
$salt = salt_row["salt"];
$unhashed_password = $password.$salt;
$password = hash("SHA256", $unhashed_password);
$exec_mysql = mysql_query("SELECT * FROM players WHERE username='".$username."' AND password='".$password."'");
if (mysql_num_rows($exec_mysql)) == 1 {
echo "execResult=login_accepted";
}
if (mysql_num_rows($exec_mysql)) == 0 {
echo "execResult=login_rejected";
}
else {
echo "execResult=error";
}
?>
I have compared my code with multiple sources, even copied whole projects from sources trying to figure out what I have done wrong, but with little success. However, there is something weird I found out. Out of pure experimentation, I put the following into controlpanel.php:
=&execResult=test
And, instead of getting "undefined", I got "test". The weird part is that I only used that one line of code; no <?php tags or anything. Moreover, if I put more code beyond that, regardless of what kind of code I entered, it would always show up literally in the Flash output. So I can now hardcode a value and return it into Flash, but that's not what I need. I need to be able to determine a value based on conditionals and have it automatically returned into Flash.
Can anyone help me out? I am truly stumped.
Thanks :)
Seems that you are executing flash from the editor/debugger, (if you are checking the results with trace function).
Then using a relative url for request "php/controlpanel.php" is like opening the file localy without the execution from server of php directives.
Maybe thats why you get literaly all php code, or the correct value when you only put "=&execResult=test" as the content of the php file.
Try using absolute url with the http:// in order to perform an http request where server and php execution are involved.
I'd like to create a php script that runs as a daily cron. What I'd like to do is enumerate through all users within an Active Directory, extract certain fields from each entry, and use this information to update fields within a MySQL database.
Basically what I want to to do is sync up certain user information between Active Directory and a MySQL table.
The problem I have is that the sizelimit on the Active Directory server is often set at 1000 entries per search result. I had hoped that the php function "ldap_next_entry" would get around this by only fetching one entry at a time, but before you can call "ldap_next_entry", you first have to call "ldap_search", which can trigger the SizeLimit exceeded error.
Is there any way besides removing the sizelimit from the server? Can I somehow get "pages" of results?
BTW - I am currently not using any 3rd party libraries or code. Just PHPs ldap methods. Although, I am certainly open to using a library if that will help.
I've been struck by the same problem while developing Zend_Ldap for the Zend Framework. I'll try to explain what the real problem is, but to make it short: until PHP 5.4, it wasn't possible to use paged results from an Active Directory with an unpatched PHP (ext/ldap) version due to limitations in exactly this extension.
Let's try to unravel the whole thing... Microsoft Active Directory uses a so called server control to accomplish server-side result paging. This control ist described in RFC 2696 "LDAP Control Extension for Simple Paged Results Manipulation" .
ext/php offers an access to LDAP control extensions via its ldap_set_option() and the LDAP_OPT_SERVER_CONTROLS and LDAP_OPT_CLIENT_CONTROLS option respectively. To set the paged control you do need the control-oid, which is 1.2.840.113556.1.4.319, and we need to know how to encode the control-value (this is described in the RFC). The value is an octet string wrapping the BER-encoded version of the following SEQUENCE (copied from the RFC):
realSearchControlValue ::= SEQUENCE {
size INTEGER (0..maxInt),
-- requested page size from client
-- result set size estimate from server
cookie OCTET STRING
}
So we can set the appropriate server control prior to executing the LDAP query:
$pageSize = 100;
$pageControl = array(
'oid' => '1.2.840.113556.1.4.319', // the control-oid
'iscritical' => true, // the operation should fail if the server is not able to support this control
'value' => sprintf ("%c%c%c%c%c%c%c", 48, 5, 2, 1, $pageSize, 4, 0) // the required BER-encoded control-value
);
This allows us to send a paged query to the LDAP/AD server. But how do we know if there are more pages to follow and how do we specify with which control-value we have to send our next query?
This is where we're getting stuck... The server responds with a result set that includes the required paging information but PHP lacks a method to retrieve exactly this information from the result set. PHP provides a wrapper for the LDAP API function ldap_parse_result() but the required last parameter serverctrlsp is not exposed to the PHP function, so there is no way to retrieve the required information. A bug report has been filed for this issue but there has been no response since 2005. If the ldap_parse_result() function provided the required parameter, using paged results would work like
$l = ldap_connect('somehost.mydomain.com');
$pageSize = 100;
$pageControl = array(
'oid' => '1.2.840.113556.1.4.319',
'iscritical' => true,
'value' => sprintf ("%c%c%c%c%c%c%c", 48, 5, 2, 1, $pageSize, 4, 0)
);
$controls = array($pageControl);
ldap_set_option($l, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_bind($l, 'CN=bind-user,OU=my-users,DC=mydomain,DC=com', 'bind-user-password');
$continue = true;
while ($continue) {
ldap_set_option($l, LDAP_OPT_SERVER_CONTROLS, $controls);
$sr = ldap_search($l, 'OU=some-ou,DC=mydomain,DC=com', 'cn=*', array('sAMAccountName'), null, null, null, null);
ldap_parse_result ($l, $sr, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls); // (*)
if (isset($serverctrls)) {
foreach ($serverctrls as $i) {
if ($i["oid"] == '1.2.840.113556.1.4.319') {
$i["value"]{8} = chr($pageSize);
$i["iscritical"] = true;
$controls = array($i);
break;
}
}
}
$info = ldap_get_entries($l, $sr);
if ($info["count"] < $pageSize) {
$continue = false;
}
for ($entry = ldap_first_entry($l, $sr); $entry != false; $entry = ldap_next_entry($l, $entry)) {
$dn = ldap_get_dn($l, $entry);
}
}
As you see there is a single line of code (*) that renders the whole thing useless. On my way though the sparse information on this subject I found a patch against the PHP 4.3.10 ext/ldap by IƱaki Arenaza but neither did I try it nor do I know if the patch can be applied on a PHP5 ext/ldap. The patch extends ldap_parse_result() to expose the 7th parameter to PHP:
--- ldap.c 2004-06-01 23:05:33.000000000 +0200
+++ /usr/src/php4/php4-4.3.10/ext/ldap/ldap.c 2005-09-03 17:02:03.000000000 +0200
## -74,7 +74,7 ##
ZEND_DECLARE_MODULE_GLOBALS(ldap)
static unsigned char third_argument_force_ref[] = { 3, BYREF_NONE, BYREF_NONE, BYREF_FORCE };
-static unsigned char arg3to6of6_force_ref[] = { 6, BYREF_NONE, BYREF_NONE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE };
+static unsigned char arg3to7of7_force_ref[] = { 7, BYREF_NONE, BYREF_NONE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE };
static int le_link, le_result, le_result_entry, le_ber_entry;
## -124,7 +124,7 ##
#if ( LDAP_API_VERSION > 2000 ) || HAVE_NSLDAP
PHP_FE(ldap_get_option, third_argument_force_ref)
PHP_FE(ldap_set_option, NULL)
- PHP_FE(ldap_parse_result, arg3to6of6_force_ref)
+ PHP_FE(ldap_parse_result, arg3to7of7_force_ref)
PHP_FE(ldap_first_reference, NULL)
PHP_FE(ldap_next_reference, NULL)
#ifdef HAVE_LDAP_PARSE_REFERENCE
## -1775,14 +1775,15 ##
Extract information from result */
PHP_FUNCTION(ldap_parse_result)
{
- pval **link, **result, **errcode, **matcheddn, **errmsg, **referrals;
+ pval **link, **result, **errcode, **matcheddn, **errmsg, **referrals, **serverctrls;
ldap_linkdata *ld;
LDAPMessage *ldap_result;
+ LDAPControl **lserverctrls, **ctrlp, *ctrl;
char **lreferrals, **refp;
char *lmatcheddn, *lerrmsg;
int rc, lerrcode, myargcount = ZEND_NUM_ARGS();
- if (myargcount 6 || zend_get_parameters_ex(myargcount, &link, &result, &errcode, &matcheddn, &errmsg, &referrals) == FAILURE) {
+ if (myargcount 7 || zend_get_parameters_ex(myargcount, &link, &result, &errcode, &matcheddn, &errmsg, &referrals, &serverctrls) == FAILURE) {
WRONG_PARAM_COUNT;
}
## -1793,7 +1794,7 ##
myargcount > 3 ? &lmatcheddn : NULL,
myargcount > 4 ? &lerrmsg : NULL,
myargcount > 5 ? &lreferrals : NULL,
- NULL /* &serverctrls */,
+ myargcount > 6 ? &lserverctrls : NULL,
0 );
if (rc != LDAP_SUCCESS ) {
php_error(E_WARNING, "%s(): Unable to parse result: %s", get_active_function_name(TSRMLS_C), ldap_err2string(rc));
## -1805,6 +1806,29 ##
/* Reverse -> fall through */
switch(myargcount) {
+ case 7 :
+ zval_dtor(*serverctrls);
+
+ if (lserverctrls != NULL) {
+ array_init(*serverctrls);
+ ctrlp = lserverctrls;
+
+ while (*ctrlp != NULL) {
+ zval *ctrl_array;
+
+ ctrl = *ctrlp;
+ MAKE_STD_ZVAL(ctrl_array);
+ array_init(ctrl_array);
+
+ add_assoc_string(ctrl_array, "oid", ctrl->ldctl_oid,1);
+ add_assoc_bool(ctrl_array, "iscritical", ctrl->ldctl_iscritical);
+ add_assoc_stringl(ctrl_array, "value", ctrl->ldctl_value.bv_val,
+ ctrl->ldctl_value.bv_len,1);
+ add_next_index_zval (*serverctrls, ctrl_array);
+ ctrlp++;
+ }
+ ldap_controls_free (lserverctrls);
+ }
case 6 :
zval_dtor(*referrals);
if (array_init(*referrals) == FAILURE) {
Actually the only option left would be to change the Active Directory configuration and raise the maximum result limit. The relevant option is called MaxPageSize and can be altered by using ntdsutil.exe - please see "How to view and set LDAP policy in Active Directory by using Ntdsutil.exe".
EDIT (reference to COM):
Or you can go the other way round and use the COM-approach via ADODB as suggested in the link provided by eykanal.
Support for paged results was added in PHP 5.4.
See ldap_control_paged_result for more details.
This isn't a full answer, but this guy was able to do it. I don't understand what he did, though.
By the way, a partial answer is that you CAN get "pages" of results. From the documentation:
resource ldap_search ( resource $link_identifier , string $base_dn ,
string $filter [, array $attributes [, int $attrsonly [, int $sizelimit [,
int $timelimit [, int $deref ]]]]] )
...
sizelimit Enables you to limit the count of entries fetched. Setting this to 0 means no limit.
Note: This parameter can NOT override server-side preset sizelimit.
You can set it lower though. Some directory server hosts will be
configured to return no more than a preset number of entries. If this
occurs, the server will indicate that it has only returned a partial
results set. This also occurs if you use this parameter to limit the
count of fetched entries.
I don't know how to specify that you want to search STARTING from a certain position, though. I.e., after you get your first 1000, I don't know how to specify that now you need the next 1000. Hopefully someone else can help you there :)
Here's an alternative (which works pre PHP 5.4). If you have 10,000 records you need to get but your AD server only returns 5,000 per page:
$ldapSearch = ldap_search($ldapResource, $basedn, $filter, array('member;range=0-4999'));
$ldapResults = ldap_get_entries($dn, $ldapSearch);
$members = $ldapResults[0]['member;range=0-4999'];
$ldapSearch = ldap_search($ldapResource, $basedn, $filter, array('member;range=5000-10000'));
$ldapResults = ldap_get_entries($dn, $ldapSearch);
$members = array_merge($members, $ldapResults[0]['member;range=5000-*']);
I was able to get around the size limitation using ldap_control_paged_result
ldap_control_paged_result is used to Enable LDAP pagination by sending the pagination control. The below function worked perfectly in my case.
function retrieves_users($conn)
{
$dn = 'ou=,dc=,dc=';
$filter = "(&(objectClass=user)(objectCategory=person)(sn=*))";
$justthese = array();
// enable pagination with a page size of 100.
$pageSize = 100;
$cookie = '';
do {
ldap_control_paged_result($conn, $pageSize, true, $cookie);
$result = ldap_search($conn, $dn, $filter, $justthese);
$entries = ldap_get_entries($conn, $result);
if(!empty($entries)){
for ($i = 0; $i < $entries["count"]; $i++) {
$data['usersLdap'][] = array(
'name' => $entries[$i]["cn"][0],
'username' => $entries[$i]["userprincipalname"][0]
);
}
}
ldap_control_paged_result_response($conn, $result, $cookie);
} while($cookie !== null && $cookie != '');
return $data;
}