Git.pm: add interface for git credential command
Add a credential() function which is an interface to the git credential command. The code is heavily based on credential_* functions in <contrib/mw-to-git/git-remote-mediawiki>. Signed-off-by: Michal Nazarewicz <mina86@mina86.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Michal Nazarewicz committed
Feb 12, 2013 at 15:02 UTC
52dce6d036f1f3859062c475127e498ab3d261b9
1 file changed
+151
perl/Git.pm
+151
@@ -59,6 +59,7 @@ require Exporter;
59
command_bidi_pipe command_close_bidi_pipe
60
version exec_path html_path hash_object git_cmd_try
61
remote_refs prompt
62
+ credential credential_read credential_write
63
temp_acquire temp_release temp_reset temp_path);
64
65
@@ -1003,6 +1004,156 @@ sub _close_cat_blob {
1004
}
1005
1006
1007
+=item credential_read( FILEHANDLE )
1008
+
1009
+Reads credential key-value pairs from C<FILEHANDLE>. Reading stops at EOF or
1010
+when an empty line is encountered. Each line must be of the form C<key=value>
1011
+with a non-empty key. Function returns hash with all read values. Any white
1012
+space (other than new-line character) is preserved.
1013
+
1014
+=cut
1015
+
1016
+sub credential_read {
1017
+ my ($self, $reader) = _maybe_self(@_);
1018
+ my %credential;
1019
+ while (<$reader>) {
1020
+ chomp;
1021
+ if ($_ eq '') {
1022
+ last;
1023
+ } elsif (!/^([^=]+)=(.*)$/) {
1024
+ throw Error::Simple("unable to parse git credential data:\n$_");
1025
+ }
1026
+ $credential{$1} = $2;
1027
+ }
1028
+ return %credential;
1029
+}
1030
+
1031
+=item credential_write( FILEHANDLE, CREDENTIAL_HASHREF )
1032
+
1033
+Writes credential key-value pairs from hash referenced by
1034
+C<CREDENTIAL_HASHREF> to C<FILEHANDLE>. Keys and values cannot contain
1035
+new-lines or NUL bytes characters, and key cannot contain equal signs nor be
1036
+empty (if they do Error::Simple is thrown). Any white space is preserved. If
1037
+value for a key is C<undef>, it will be skipped.
1038
+
1039
+If C<'url'> key exists it will be written first. (All the other key-value
1040
+pairs are written in sorted order but you should not depend on that). Once
1041
+all lines are written, an empty line is printed.
1042
+
1043
+=cut
1044
+
1045
+sub credential_write {
1046
+ my ($self, $writer, $credential) = _maybe_self(@_);
1047
+ my ($key, $value);
1048
+
1049
+ # Check if $credential is valid prior to writing anything
1050
+ while (($key, $value) = each %$credential) {
1051
+ if (!defined $key || !length $key) {
1052
+ throw Error::Simple("credential key empty or undefined");
1053
+ } elsif ($key =~ /[=\n\0]/) {
1054
+ throw Error::Simple("credential key contains invalid characters: $key");
1055
+ } elsif (defined $value && $value =~ /[\n\0]/) {
1056
+ throw Error::Simple("credential value for key=$key contains invalid characters: $value");
1057
+ }
1058
+ }
1059
+
1060
+ for $key (sort {
1061
+ # url overwrites other fields, so it must come first
1062
+ return -1 if $a eq 'url';
1063
+ return 1 if $b eq 'url';
1064
+ return $a cmp $b;
1065
+ } keys %$credential) {
1066
+ if (defined $credential->{$key}) {
1067
+ print $writer $key, '=', $credential->{$key}, "\n";
1068
+ }
1069
+ }
1070
+ print $writer "\n";
1071
+}
1072
+
1073
+sub _credential_run {
1074
+ my ($self, $credential, $op) = _maybe_self(@_);
1075
+ my ($pid, $reader, $writer, $ctx) = command_bidi_pipe('credential', $op);
1076
+
1077
+ credential_write $writer, $credential;
1078
+ close $writer;
1079
+
1080
+ if ($op eq "fill") {
1081
+ %$credential = credential_read $reader;
1082
+ }
1083
+ if (<$reader>) {
1084
+ throw Error::Simple("unexpected output from git credential $op response:\n$_\n");
1085
+ }
1086
+
1087
+ command_close_bidi_pipe($pid, $reader, undef, $ctx);
1088
+}
1089
+
1090
+=item credential( CREDENTIAL_HASHREF [, OPERATION ] )
1091
+
1092
+=item credential( CREDENTIAL_HASHREF, CODE )
1093
+
1094
+Executes C<git credential> for a given set of credentials and specified
1095
+operation. In both forms C<CREDENTIAL_HASHREF> needs to be a reference to
1096
+a hash which stores credentials. Under certain conditions the hash can
1097
+change.
1098
+
1099
+In the first form, C<OPERATION> can be C<'fill'>, C<'approve'> or C<'reject'>,
1100
+and function will execute corresponding C<git credential> sub-command. If
1101
+it's omitted C<'fill'> is assumed. In case of C<'fill'> the values stored in
1102
+C<CREDENTIAL_HASHREF> will be changed to the ones returned by the C<git
1103
+credential fill> command. The usual usage would look something like:
1104
+
1105
+ my %cred = (
1106
+ 'protocol' => 'https',
1107
+ 'host' => 'example.com',
1108
+ 'username' => 'bob'
1109
+ );
1110
+ Git::credential \%cred;
1111
+ if (try_to_authenticate($cred{'username'}, $cred{'password'})) {
1112
+ Git::credential \%cred, 'approve';
1113
+ ... do more stuff ...
1114
+ } else {
1115
+ Git::credential \%cred, 'reject';
1116
+ }
1117
+
1118
+In the second form, C<CODE> needs to be a reference to a subroutine. The
1119
+function will execute C<git credential fill> to fill the provided credential
1120
+hash, then call C<CODE> with C<CREDENTIAL_HASHREF> as the sole argument. If
1121
+C<CODE>'s return value is defined, the function will execute C<git credential
1122
+approve> (if return value yields true) or C<git credential reject> (if return
1123
+value is false). If the return value is undef, nothing at all is executed;
1124
+this is useful, for example, if the credential could neither be verified nor
1125
+rejected due to an unrelated network error. The return value is the same as
1126
+what C<CODE> returns. With this form, the usage might look as follows:
1127
+
1128
+ if (Git::credential {
1129
+ 'protocol' => 'https',
1130
+ 'host' => 'example.com',
1131
+ 'username' => 'bob'
1132
+ }, sub {
1133
+ my $cred = shift;
1134
+ return !!try_to_authenticate($cred->{'username'},
1135
+ $cred->{'password'});
1136
+ }) {
1137
+ ... do more stuff ...
1138
+ }
1139
+
1140
+=cut
1141
+
1142
+sub credential {
1143
+ my ($self, $credential, $op_or_code) = (_maybe_self(@_), 'fill');
1144
+
1145
+ if ('CODE' eq ref $op_or_code) {
1146
+ _credential_run $credential, 'fill';
1147
+ my $ret = $op_or_code->($credential);
1148
+ if (defined $ret) {
1149
+ _credential_run $credential, $ret ? 'approve' : 'reject';
1150
+ }
1151
+ return $ret;
1152
+ } else {
1153
+ _credential_run $credential, $op_or_code;
1154
+ }
1155
+}
1156
+
1157
{ # %TEMP_* Lexical Context
1158
1159
my (%TEMP_FILEMAP, %TEMP_FILES);