Skip to content

Commit 2278487

Browse files
FELIX-6390 Refactor the default authentication mechanism of the (#71)
webconsole to be a WebConsoleSecurityProvider2
1 parent 8736598 commit 2278487

File tree

4 files changed

+215
-71
lines changed

4 files changed

+215
-71
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.felix.webconsole.internal.servlet;
18+
19+
import java.io.IOException;
20+
import java.io.UnsupportedEncodingException;
21+
22+
import javax.servlet.http.HttpServletRequest;
23+
import javax.servlet.http.HttpServletResponse;
24+
25+
import org.apache.felix.webconsole.WebConsoleSecurityProvider2;
26+
import org.osgi.framework.BundleContext;
27+
import org.osgi.service.http.HttpContext;
28+
29+
/**
30+
* Basic implementation of WebConsoleSecurityProvider to replace logic that
31+
* was previously in OsgiManagerHttpContext
32+
*/
33+
public class BasicWebConsoleSecurityProvider implements WebConsoleSecurityProvider2 {
34+
35+
static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
36+
37+
static final String HEADER_AUTHORIZATION = "Authorization";
38+
39+
static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
40+
41+
private final String username;
42+
43+
private final Password password;
44+
45+
private final String realm;
46+
47+
private BundleContext bundleContext;
48+
49+
public BasicWebConsoleSecurityProvider(BundleContext bundleContext, String username, String password,
50+
String realm) {
51+
super();
52+
this.bundleContext = bundleContext;
53+
this.username = username;
54+
this.password = new Password(password);
55+
this.realm = realm;
56+
}
57+
58+
public Object authenticate(String username, String password) {
59+
if ( this.username.equals( username ) && this.password.matches( password.getBytes() ) )
60+
{
61+
if (bundleContext.getProperty(OsgiManager.FRAMEWORK_PROP_SECURITY_PROVIDERS) == null) {
62+
// Only allow username and password authentication if no mandatory security providers are registered
63+
return true;
64+
}
65+
}
66+
return null;
67+
}
68+
69+
/**
70+
* All users authenticated with the repository are granted access for all roles in the Web Console.
71+
*/
72+
@Override
73+
public boolean authorize(Object user, String role) {
74+
return true;
75+
}
76+
77+
@Override
78+
public boolean authenticate(HttpServletRequest request, HttpServletResponse response) {
79+
// Return immediately if the header is missing
80+
String authHeader = request.getHeader( HEADER_AUTHORIZATION );
81+
if ( authHeader != null && authHeader.length() > 0 )
82+
{
83+
84+
// Get the authType (Basic, Digest) and authInfo (user/password)
85+
// from
86+
// the header
87+
authHeader = authHeader.trim();
88+
int blank = authHeader.indexOf( ' ' );
89+
if ( blank > 0 )
90+
{
91+
String authType = authHeader.substring( 0, blank );
92+
String authInfo = authHeader.substring( blank ).trim();
93+
94+
// Check whether authorization type matches
95+
if ( authType.equalsIgnoreCase( AUTHENTICATION_SCHEME_BASIC ) )
96+
{
97+
try
98+
{
99+
byte[][] userPass = base64Decode( authInfo );
100+
final String username = toString( userPass[0] );
101+
102+
// authenticate
103+
if ( authenticate( username, toString(userPass[1]) ) != null )
104+
{
105+
// as per the spec, set attributes
106+
request.setAttribute( HttpContext.AUTHENTICATION_TYPE, HttpServletRequest.BASIC_AUTH );
107+
request.setAttribute( HttpContext.REMOTE_USER, username );
108+
109+
// set web console user attribute
110+
request.setAttribute( WebConsoleSecurityProvider2.USER_ATTRIBUTE, username );
111+
112+
// succeed
113+
return true;
114+
}
115+
}
116+
catch ( Exception e )
117+
{
118+
// Ignore
119+
}
120+
}
121+
}
122+
}
123+
124+
// request authentication
125+
try
126+
{
127+
response.setHeader( HEADER_WWW_AUTHENTICATE, AUTHENTICATION_SCHEME_BASIC + " realm=\"" + this.realm + "\"" );
128+
response.setStatus( HttpServletResponse.SC_UNAUTHORIZED );
129+
response.setContentLength( 0 );
130+
response.flushBuffer();
131+
}
132+
catch ( IOException ioe )
133+
{
134+
// failed sending the response ... cannot do anything about it
135+
}
136+
137+
// inform HttpService that authentication failed
138+
return false;
139+
}
140+
141+
static byte[][] base64Decode( String srcString )
142+
{
143+
byte[] transformed = Base64.decodeBase64( srcString );
144+
for ( int i = 0; i < transformed.length; i++ )
145+
{
146+
if ( transformed[i] == ':' )
147+
{
148+
byte[] user = new byte[i];
149+
byte[] pass = new byte[transformed.length - i - 1];
150+
System.arraycopy( transformed, 0, user, 0, user.length );
151+
System.arraycopy( transformed, i + 1, pass, 0, pass.length );
152+
return new byte[][]
153+
{ user, pass };
154+
}
155+
}
156+
157+
return new byte[][]
158+
{ transformed, new byte[0] };
159+
}
160+
161+
static String toString( final byte[] src )
162+
{
163+
try
164+
{
165+
return new String( src, "ISO-8859-1" );
166+
}
167+
catch ( UnsupportedEncodingException uee )
168+
{
169+
return new String( src );
170+
}
171+
}
172+
173+
}

webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ public class OsgiManager extends GenericServlet
230230

231231
private String webManagerRoot;
232232

233+
// not-null when the BasicWebConsoleSecurityProvider service is registered
234+
private ServiceRegistration<WebConsoleSecurityProvider> basicSecurityServiceRegistration;
235+
233236
// true if the OsgiManager is registered as a Servlet with the HttpService
234237
private boolean httpServletRegistered;
235238

@@ -958,11 +961,22 @@ synchronized void registerHttpService() {
958961
// register the servlet and resources
959962
try
960963
{
961-
HttpContext httpContext = new OsgiManagerHttpContext(bundleContext, httpService,
962-
securityProviderTracker, userId, password, realm);
964+
HttpContext httpContext = new OsgiManagerHttpContext(httpService,
965+
securityProviderTracker, realm);
963966

964967
Dictionary<String, String> servletConfig = toStringConfig(config);
965968

969+
if (basicSecurityServiceRegistration == null) {
970+
//register this component
971+
BasicWebConsoleSecurityProvider service = new BasicWebConsoleSecurityProvider(bundleContext,
972+
userId, password, realm);
973+
Dictionary<String, Object> serviceProperties = new Hashtable<>(); // NOSONAR
974+
// this is a last resort service, so use a low service ranking to prefer all other services over this one
975+
serviceProperties.put(Constants.SERVICE_RANKING, Integer.MIN_VALUE);
976+
basicSecurityServiceRegistration = bundleContext.registerService(WebConsoleSecurityProvider.class,
977+
service, serviceProperties);
978+
}
979+
966980
if (!httpServletRegistered) {
967981
// register this servlet and take note of this
968982
httpService.registerServlet(this.webManagerRoot, this, servletConfig,
@@ -1002,6 +1016,16 @@ synchronized void unregisterHttpService() {
10021016
if (httpService == null)
10031017
return;
10041018

1019+
if (basicSecurityServiceRegistration != null) {
1020+
try {
1021+
basicSecurityServiceRegistration.unregister();
1022+
} catch (Throwable t) {
1023+
log(LogService.LOG_WARNING,
1024+
"unbindHttpService: Failed unregistering basic WebConsoleSecurityProvider", t);
1025+
}
1026+
basicSecurityServiceRegistration = null;
1027+
}
1028+
10051029
if (httpResourcesRegistered)
10061030
{
10071031
try

webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java

Lines changed: 11 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@
1717
package org.apache.felix.webconsole.internal.servlet;
1818

1919

20+
import static org.apache.felix.webconsole.internal.servlet.BasicWebConsoleSecurityProvider.AUTHENTICATION_SCHEME_BASIC;
21+
import static org.apache.felix.webconsole.internal.servlet.BasicWebConsoleSecurityProvider.HEADER_AUTHORIZATION;
22+
import static org.apache.felix.webconsole.internal.servlet.BasicWebConsoleSecurityProvider.HEADER_WWW_AUTHENTICATE;
23+
2024
import java.io.IOException;
21-
import java.io.UnsupportedEncodingException;
2225
import java.net.URL;
26+
2327
import javax.servlet.http.HttpServletRequest;
2428
import javax.servlet.http.HttpServletResponse;
2529

2630
import org.apache.felix.webconsole.User;
2731
import org.apache.felix.webconsole.WebConsoleSecurityProvider;
2832
import org.apache.felix.webconsole.WebConsoleSecurityProvider2;
29-
import org.osgi.framework.BundleContext;
3033
import org.osgi.service.http.HttpContext;
3134
import org.osgi.service.http.HttpService;
3235
import org.osgi.util.tracker.ServiceTracker;
@@ -35,33 +38,17 @@
3538
final class OsgiManagerHttpContext implements HttpContext
3639
{
3740

38-
private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
39-
40-
private static final String HEADER_AUTHORIZATION = "Authorization";
41-
42-
private static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
43-
44-
private final BundleContext bundleContext;
45-
4641
private final HttpContext base;
4742

4843
private final ServiceTracker<WebConsoleSecurityProvider, WebConsoleSecurityProvider> tracker;
4944

50-
private final String username;
51-
52-
private final Password password;
53-
5445
private final String realm;
5546

56-
57-
OsgiManagerHttpContext(final BundleContext bundleContext,
58-
final HttpService httpService, final ServiceTracker<WebConsoleSecurityProvider, WebConsoleSecurityProvider> tracker, final String username,
59-
final String password, final String realm )
47+
OsgiManagerHttpContext(final HttpService httpService,
48+
final ServiceTracker<WebConsoleSecurityProvider, WebConsoleSecurityProvider> tracker,
49+
final String realm)
6050
{
61-
this.bundleContext = bundleContext;
6251
this.tracker = tracker;
63-
this.username = username;
64-
this.password = new Password(password);
6552
this.realm = realm;
6653
this.base = httpService.createDefaultHttpContext();
6754
}
@@ -162,8 +149,8 @@ private boolean handleSecurity( final WebConsoleSecurityProvider provider,
162149
{
163150
try
164151
{
165-
byte[][] userPass = base64Decode( authInfo );
166-
final String username = toString( userPass[0] );
152+
byte[][] userPass = BasicWebConsoleSecurityProvider.base64Decode( authInfo );
153+
final String username = BasicWebConsoleSecurityProvider.toString( userPass[0] );
167154

168155
// authenticate
169156
if ( authenticate( provider, username, userPass[1] ) )
@@ -204,52 +191,11 @@ private boolean handleSecurity( final WebConsoleSecurityProvider provider,
204191
return false;
205192
}
206193

207-
private static byte[][] base64Decode( String srcString )
208-
{
209-
byte[] transformed = Base64.decodeBase64( srcString );
210-
for ( int i = 0; i < transformed.length; i++ )
211-
{
212-
if ( transformed[i] == ':' )
213-
{
214-
byte[] user = new byte[i];
215-
byte[] pass = new byte[transformed.length - i - 1];
216-
System.arraycopy( transformed, 0, user, 0, user.length );
217-
System.arraycopy( transformed, i + 1, pass, 0, pass.length );
218-
return new byte[][]
219-
{ user, pass };
220-
}
221-
}
222-
223-
return new byte[][]
224-
{ transformed, new byte[0] };
225-
}
226-
227-
228-
private static String toString( final byte[] src )
229-
{
230-
try
231-
{
232-
return new String( src, "ISO-8859-1" );
233-
}
234-
catch ( UnsupportedEncodingException uee )
235-
{
236-
return new String( src );
237-
}
238-
}
239-
240-
241194
private boolean authenticate( WebConsoleSecurityProvider provider, String username, byte[] password )
242195
{
243196
if ( provider != null )
244197
{
245-
return provider.authenticate( username, toString( password ) ) != null;
246-
}
247-
if ( this.username.equals( username ) && this.password.matches( password ) )
248-
{
249-
if (bundleContext.getProperty(OsgiManager.FRAMEWORK_PROP_SECURITY_PROVIDERS) == null) {
250-
// Only allow username and password authentication if no mandatory security providers are registered
251-
return true;
252-
}
198+
return provider.authenticate( username, BasicWebConsoleSecurityProvider.toString( password ) ) != null;
253199
}
254200
return false;
255201
}

webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContextTest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,15 @@ public class OsgiManagerHttpContextTest {
3333
public void testAuthenticate() throws Exception {
3434
BundleContext bc = Mockito.mock(BundleContext.class);
3535
HttpService svc = Mockito.mock(HttpService.class);
36-
OsgiManagerHttpContext ctx = new OsgiManagerHttpContext(bc, svc, null, "foo", "bar", "blah");
36+
OsgiManagerHttpContext ctx = new OsgiManagerHttpContext(svc, null, "blah");
3737

3838
Method authenticateMethod = OsgiManagerHttpContext.class.getDeclaredMethod(
3939
"authenticate", new Class [] {WebConsoleSecurityProvider.class, String.class, byte[].class});
4040
authenticateMethod.setAccessible(true);
4141

42-
assertEquals(true, authenticateMethod.invoke(ctx, null, "foo", "bar".getBytes()));
43-
assertEquals(false, authenticateMethod.invoke(ctx, null, "foo", "blah".getBytes()));
42+
BasicWebConsoleSecurityProvider lastResortSp = new BasicWebConsoleSecurityProvider(bc, "foo", "bar", "blah");
43+
assertEquals(true, authenticateMethod.invoke(ctx, lastResortSp, "foo", "bar".getBytes()));
44+
assertEquals(false, authenticateMethod.invoke(ctx, lastResortSp, "foo", "blah".getBytes()));
4445

4546
WebConsoleSecurityProvider sp = new TestSecurityProvider();
4647
assertEquals(true, authenticateMethod.invoke(ctx, sp, "xxx", "yyy".getBytes()));
@@ -54,7 +55,7 @@ public void testAuthenticatePwdDisabledWithRequiredSecurityProvider() throws Exc
5455
Mockito.when(bc.getProperty(OsgiManager.FRAMEWORK_PROP_SECURITY_PROVIDERS)).thenReturn("a");
5556

5657
HttpService svc = Mockito.mock(HttpService.class);
57-
OsgiManagerHttpContext ctx = new OsgiManagerHttpContext(bc, svc, null, "foo", "bar", "blah");
58+
OsgiManagerHttpContext ctx = new OsgiManagerHttpContext(svc, null, "blah");
5859

5960
Method authenticateMethod = OsgiManagerHttpContext.class.getDeclaredMethod(
6061
"authenticate", new Class [] {WebConsoleSecurityProvider.class, String.class, byte[].class});

0 commit comments

Comments
 (0)