Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
佳 邓
Gitlab4j Api
Commits
f027fd44
Commit
f027fd44
authored
Apr 21, 2019
by
Greg Messner
Browse files
Initial commit (#310).
parent
77cd763f
Changes
1
Hide whitespace changes
Inline
Side-by-side
src/main/java/org/gitlab4j/api/utils/MaskingLoggingFilter.java
0 → 100644
View file @
f027fd44
package
org.gitlab4j.api.utils
;
import
java.io.BufferedInputStream
;
import
java.io.ByteArrayOutputStream
;
import
java.io.FilterOutputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.OutputStream
;
import
java.net.URI
;
import
java.nio.charset.Charset
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.Map.Entry
;
import
java.util.Set
;
import
java.util.TreeSet
;
import
java.util.concurrent.atomic.AtomicLong
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
import
javax.annotation.Priority
;
import
javax.ws.rs.WebApplicationException
;
import
javax.ws.rs.client.ClientRequestContext
;
import
javax.ws.rs.client.ClientRequestFilter
;
import
javax.ws.rs.client.ClientResponseContext
;
import
javax.ws.rs.client.ClientResponseFilter
;
import
javax.ws.rs.core.MediaType
;
import
javax.ws.rs.core.MultivaluedMap
;
import
javax.ws.rs.ext.WriterInterceptor
;
import
javax.ws.rs.ext.WriterInterceptorContext
;
import
org.glassfish.jersey.message.MessageUtils
;
/**
* This class logs request and response info masking HTTP header values that are known to
* contain sensitive information.
*
* This class was patterned after org.glassfish.jersey.logging.LoggingInterceptor, but written in
* such a way that it could be sub-classed and have its behavior modified.
*/
@Priority
(
Integer
.
MIN_VALUE
)
public
class
MaskingLoggingFilter
implements
ClientRequestFilter
,
ClientResponseFilter
,
WriterInterceptor
{
/**
* Default list of header names that should be masked.
*/
public
static
final
List
<
String
>
DEFAULT_MASKED_HEADER_NAMES
=
Collections
.
unmodifiableList
(
Arrays
.
asList
(
"PRIVATE-TOKEN"
,
"Authorization"
));
/**
* Prefix for request log entries.
*/
protected
static
final
String
REQUEST_PREFIX
=
"> "
;
/**
* Prefix for response log entries.
*/
protected
static
final
String
RESPONSE_PREFIX
=
"< "
;
/**
* Prefix that marks the beginning of a request or response section.
*/
protected
static
final
String
SECTION_PREFIX
=
"- "
;
/**
* Property name for the entity stream property
*/
protected
static
final
String
ENTITY_STREAM_PROPERTY
=
MaskingLoggingFilter
.
class
.
getName
()
+
".entityStream"
;
/**
* Property name for the logging record id property
*/
protected
static
final
String
LOGGING_ID_PROPERTY
=
MaskingLoggingFilter
.
class
.
getName
()
+
".id"
;
protected
final
Logger
logger
;
protected
final
Level
level
;
protected
final
int
maxEntitySize
;
protected
final
AtomicLong
_id
=
new
AtomicLong
(
0
);
protected
Set
<
String
>
maskedHeaderNames
=
new
HashSet
<
String
>();
/**
* Creates a masking logging filter for the specified logger with entity logging disabled.
*
* @param logger the logger to log messages to
* @param level level at which the messages will be logged
*/
public
MaskingLoggingFilter
(
final
Logger
logger
,
final
Level
level
)
{
this
(
logger
,
level
,
0
,
null
);
}
/**
* Creates a masking logging filter for the specified logger.
*
* @param logger the logger to log messages to
* @param level level at which the messages will be logged
* @param maxEntitySize maximum number of entity bytes to be logged. When logging if the maxEntitySize
* is reached, the entity logging will be truncated at maxEntitySize and "...more..." will be added at
* the end of the log entry. If maxEntitySize is <= 0, entity logging will be disabled
*/
public
MaskingLoggingFilter
(
final
Logger
logger
,
final
Level
level
,
final
int
maxEntitySize
)
{
this
(
logger
,
level
,
maxEntitySize
,
null
);
}
/**
* Creates a masking logging filter for the specified logger with entity logging disabled.
*
* @param logger the logger to log messages to
* @param level level at which the messages will be logged
* @param maskedHeaderNames a list of header names that should have the values masked
*/
public
MaskingLoggingFilter
(
final
Logger
logger
,
final
Level
level
,
final
List
<
String
>
maskedHeaderNames
)
{
this
(
logger
,
level
,
0
,
maskedHeaderNames
);
}
/**
* Creates a masking logging filter for the specified logger.
*
* @param logger the logger to log messages to
* @param level level at which the messages will be logged
* @param maxEntitySize maximum number of entity bytes to be logged. When logging if the maxEntitySize
* is reached, the entity logging will be truncated at maxEntitySize and "...more..." will be added at
* the end of the log entry. If maxEntitySize is <= 0, entity logging will be disabled
* @param maskedHeaderNames a list of header names that should have the values masked
*/
public
MaskingLoggingFilter
(
final
Logger
logger
,
final
Level
level
,
final
int
maxEntitySize
,
final
List
<
String
>
maskedHeaderNames
)
{
this
.
logger
=
logger
;
this
.
level
=
level
;
this
.
maxEntitySize
=
maxEntitySize
;
if
(
maskedHeaderNames
!=
null
)
{
maskedHeaderNames
.
forEach
(
h
->
this
.
maskedHeaderNames
.
add
(
h
.
toLowerCase
()));
}
}
/**
* Set the list of header names to mask values for. If null, will clear the header names to mask.
*
* @param maskedHeaderNames a list of header names that should have the values masked, if null, will clear
* the header names to mask
*/
public
void
setMaskedHeaderNames
(
final
List
<
String
>
maskedHeaderNames
)
{
this
.
maskedHeaderNames
.
clear
();
if
(
maskedHeaderNames
!=
null
)
{
maskedHeaderNames
.
forEach
(
h
->
{
addMaskedHeaderName
(
h
);
});
}
}
/**
* Add a header name to the list of masked header names.
*
* @param maskedHeaderName the masked header name to add
*/
public
void
addMaskedHeaderName
(
String
maskedHeaderName
)
{
if
(
maskedHeaderName
!=
null
)
{
maskedHeaderName
=
maskedHeaderName
.
trim
();
if
(
maskedHeaderName
.
length
()
>
0
)
{
maskedHeaderNames
.
add
(
maskedHeaderName
.
toLowerCase
());
}
}
}
protected
void
log
(
final
StringBuilder
sb
)
{
if
(
logger
!=
null
)
{
logger
.
log
(
level
,
sb
.
toString
());
}
}
protected
StringBuilder
appendId
(
final
StringBuilder
sb
,
final
long
id
)
{
sb
.
append
(
Long
.
toString
(
id
)).
append
(
' '
);
return
(
sb
);
}
protected
void
printRequestLine
(
final
StringBuilder
sb
,
final
String
note
,
final
long
id
,
final
String
method
,
final
URI
uri
)
{
appendId
(
sb
,
id
).
append
(
SECTION_PREFIX
)
.
append
(
note
)
.
append
(
" on thread "
).
append
(
Thread
.
currentThread
().
getName
())
.
append
(
'\n'
);
appendId
(
sb
,
id
).
append
(
REQUEST_PREFIX
).
append
(
method
).
append
(
' '
)
.
append
(
uri
.
toASCIIString
()).
append
(
'\n'
);
}
protected
void
printResponseLine
(
final
StringBuilder
sb
,
final
String
note
,
final
long
id
,
final
int
status
)
{
appendId
(
sb
,
id
).
append
(
SECTION_PREFIX
)
.
append
(
note
)
.
append
(
" on thread "
).
append
(
Thread
.
currentThread
().
getName
()).
append
(
'\n'
);
appendId
(
sb
,
id
).
append
(
RESPONSE_PREFIX
)
.
append
(
Integer
.
toString
(
status
))
.
append
(
'\n'
);
}
protected
Set
<
Entry
<
String
,
List
<
String
>>>
getSortedHeaders
(
final
Set
<
Entry
<
String
,
List
<
String
>>>
headers
)
{
final
TreeSet
<
Entry
<
String
,
List
<
String
>>>
sortedHeaders
=
new
TreeSet
<
Entry
<
String
,
List
<
String
>>>(
(
Entry
<
String
,
List
<
String
>>
o1
,
Entry
<
String
,
List
<
String
>>
o2
)
->
o1
.
getKey
().
compareToIgnoreCase
(
o2
.
getKey
()));
sortedHeaders
.
addAll
(
headers
);
return
sortedHeaders
;
}
/**
* Logs each of the HTTP headers, masking the value of the header if the header key is
* in the list of masked header names.
*
* @param sb the StringBuilder to build up the logging info in
* @param id the ID for the logging line
* @param prefix the logging line prefix character
* @param headers a MultiValue map holding the header keys and values
*/
protected
void
printHeaders
(
final
StringBuilder
sb
,
final
long
id
,
final
String
prefix
,
final
MultivaluedMap
<
String
,
String
>
headers
)
{
getSortedHeaders
(
headers
.
entrySet
()).
forEach
(
h
->
{
final
List
<?>
values
=
h
.
getValue
();
final
String
header
=
h
.
getKey
();
final
boolean
isMaskedHeader
=
maskedHeaderNames
.
contains
(
header
.
toLowerCase
());
if
(
values
.
size
()
==
1
)
{
String
value
=
(
isMaskedHeader
?
"********"
:
values
.
get
(
0
).
toString
());
appendId
(
sb
,
id
).
append
(
prefix
).
append
(
header
).
append
(
": "
).
append
(
value
).
append
(
'\n'
);
}
else
{
final
StringBuilder
headerBuf
=
new
StringBuilder
();
for
(
final
Object
value
:
values
)
{
if
(
headerBuf
.
length
()
==
0
)
{
headerBuf
.
append
(
", "
);
}
headerBuf
.
append
(
isMaskedHeader
?
"********"
:
value
.
toString
());
}
appendId
(
sb
,
id
).
append
(
prefix
).
append
(
header
).
append
(
": "
).
append
(
headerBuf
.
toString
()).
append
(
'\n'
);
}
});
}
protected
void
buildEntityLogString
(
StringBuilder
sb
,
byte
[]
entity
,
int
entitySize
,
Charset
charset
)
{
sb
.
append
(
new
String
(
entity
,
0
,
Math
.
min
(
entitySize
,
maxEntitySize
),
charset
));
if
(
entitySize
>
maxEntitySize
)
{
sb
.
append
(
"...more..."
);
}
sb
.
append
(
'\n'
);
}
private
InputStream
logResponseEntity
(
final
StringBuilder
sb
,
InputStream
stream
,
final
Charset
charset
)
throws
IOException
{
if
(
maxEntitySize
<=
0
)
{
return
(
stream
);
}
if
(!
stream
.
markSupported
())
{
stream
=
new
BufferedInputStream
(
stream
);
}
stream
.
mark
(
maxEntitySize
+
1
);
final
byte
[]
entity
=
new
byte
[
maxEntitySize
+
1
];
final
int
entitySize
=
stream
.
read
(
entity
);
buildEntityLogString
(
sb
,
entity
,
entitySize
,
charset
);
stream
.
reset
();
return
stream
;
}
@Override
public
void
filter
(
ClientRequestContext
requestContext
)
throws
IOException
{
if
(!
logger
.
isLoggable
(
level
))
{
return
;
}
final
long
id
=
_id
.
incrementAndGet
();
requestContext
.
setProperty
(
LOGGING_ID_PROPERTY
,
id
);
final
StringBuilder
sb
=
new
StringBuilder
();
printRequestLine
(
sb
,
"Sending client request"
,
id
,
requestContext
.
getMethod
(),
requestContext
.
getUri
());
printHeaders
(
sb
,
id
,
REQUEST_PREFIX
,
requestContext
.
getStringHeaders
());
if
(
requestContext
.
hasEntity
()
&&
maxEntitySize
>
0
)
{
final
OutputStream
stream
=
new
LoggingStream
(
sb
,
requestContext
.
getEntityStream
());
requestContext
.
setEntityStream
(
stream
);
requestContext
.
setProperty
(
ENTITY_STREAM_PROPERTY
,
stream
);
}
else
{
log
(
sb
);
}
}
@Override
public
void
filter
(
ClientRequestContext
requestContext
,
ClientResponseContext
responseContext
)
throws
IOException
{
if
(!
logger
.
isLoggable
(
level
))
{
return
;
}
final
Object
requestId
=
requestContext
.
getProperty
(
LOGGING_ID_PROPERTY
);
final
long
id
=
requestId
!=
null
?
(
Long
)
requestId
:
_id
.
incrementAndGet
();
final
StringBuilder
sb
=
new
StringBuilder
();
printResponseLine
(
sb
,
"Received server response"
,
id
,
responseContext
.
getStatus
());
printHeaders
(
sb
,
id
,
RESPONSE_PREFIX
,
responseContext
.
getHeaders
());
if
(
responseContext
.
hasEntity
()
&&
maxEntitySize
>
0
)
{
responseContext
.
setEntityStream
(
logResponseEntity
(
sb
,
responseContext
.
getEntityStream
(),
MessageUtils
.
getCharset
(
responseContext
.
getMediaType
())));
}
log
(
sb
);
}
@Override
public
void
aroundWriteTo
(
WriterInterceptorContext
context
)
throws
IOException
,
WebApplicationException
{
final
LoggingStream
stream
=
(
LoggingStream
)
context
.
getProperty
(
ENTITY_STREAM_PROPERTY
);
context
.
proceed
();
if
(
stream
==
null
)
{
return
;
}
MediaType
mediaType
=
context
.
getMediaType
();
if
(
mediaType
.
isCompatible
(
MediaType
.
APPLICATION_JSON_TYPE
)
||
mediaType
.
isCompatible
(
MediaType
.
APPLICATION_FORM_URLENCODED_TYPE
))
{
log
(
stream
.
getStringBuilder
(
MessageUtils
.
getCharset
(
mediaType
)));
}
}
/**
* This class is responsible for logging the request entities, it will truncate at maxEntitySize
* and add "...more..." to the end of the entity log string.
*/
protected
class
LoggingStream
extends
FilterOutputStream
{
private
final
StringBuilder
sb
;
private
final
ByteArrayOutputStream
outputStream
=
new
ByteArrayOutputStream
();
LoggingStream
(
StringBuilder
sb
,
OutputStream
out
)
{
super
(
out
);
this
.
sb
=
sb
;
}
StringBuilder
getStringBuilder
(
Charset
charset
)
{
final
byte
[]
entity
=
outputStream
.
toByteArray
();
buildEntityLogString
(
sb
,
entity
,
entity
.
length
,
charset
);
return
(
sb
);
}
@Override
public
void
write
(
final
int
i
)
throws
IOException
{
if
(
outputStream
.
size
()
<=
maxEntitySize
)
{
outputStream
.
write
(
i
);
}
out
.
write
(
i
);
}
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment