Wednesday, January 20, 2010

WBem, Wiseman, Windows, Java and Negotiate authentication

I was playing around with WBem and tried to access the Windows Remote stuff on my Vista laptop. To be special Mocrosoft is calling their WBem implementation Windows Remote (WinRM). Because it's complicate here is some good blog entry on how to make WinRM work for you.

Back to Wiseman: Denis Rachal has posted some code to start/stop a service using Wiseman and WinRM to the Wiseman's user's mailing list on 6/18/2009. The problem with Wiseman is that you need to set up your WinRM to use unencrypted communication and basic authentication. Because the encryption is relatively new and "invented" by MS called "HTTP-SPNEGO-session-encrypted" the RFC (Google cache -- as you might expect from MS:-) is kind-a cryptic...

But we can do something about the authentication and use Negotiate by using the SpnegoHttpUrlConnection implementation from the SPNEGO project. Make sure to read through all their pre-flight documentation to set up kerberos and so on (keyword: krb5.conf, login.conf) The account you use needs to be at least a local administrator and it is helpful to prefix the domain for the user name (MY_DOMAIN\MY-USERNAME) Once this works you can rework Denis' example and have it support the Negotiate protocol as I did:


/**
* Copyright (C) 2006-2009 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Authors: Denis Rachal (denis.rachal@hp.com)
*/
package com.hp.wsman.client.transport;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.net.URLConnection;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedActionException;
import java.security.SecureRandom;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.login.LoginException;
import javax.xml.bind.JAXBException;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;

import org.ietf.jgss.GSSException;

import net.sourceforge.spnego.SpnegoHttpURLConnection;

import sun.misc.BASE64Encoder;

import com.sun.ws.management.Message;
import com.sun.ws.management.addressing.Addressing;
import com.sun.ws.management.transport.ContentType;

public final class HttpClient {

private static final Logger LOG = Logger.getLogger(HttpClient.class
.getName());
private static PasswordAuthentication auth;

private HttpClient() {
}

static {
System.setProperty("java.security.krb5.conf", "C:\\Users\\eichbege\\wiseman-client\\client\\src\\krb5.conf");
System.setProperty("sun.security.krb5.debug", "false");
System.setProperty("java.security.auth.login.config", "C:\\Users\\eichbege\\wiseman-client\\client\\src\\login.conf");
}

static class MyAuthenticator extends Authenticator {
public PasswordAuthentication getPasswordAuthentication() {
// I haven't checked getRequestingScheme() here, since for NTLM
// and Negotiate, the usrname and password are all the same.
System.err.println("Feeding username and password for " + getRequestingScheme());
return auth;
}
}


public static void setPasswordAuthentication(
final PasswordAuthentication pauth) {
auth = pauth;
}


public static void setTrustManager(final X509TrustManager trustManager)
throws NoSuchAlgorithmException, KeyManagementException {

final TrustManager[] tm = { trustManager };
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, tm, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext
.getSocketFactory());
}

public static void setHostnameVerifier(final HostnameVerifier hv) {
HttpsURLConnection.setDefaultHostnameVerifier(hv);
}

private static HttpURLConnection initConnection(final String to,
final ContentType ct, Object msg) throws IOException {
if (to == null) {
throw new IllegalArgumentException("Required Element is missing: "
+ Addressing.TO);
}

if (auth != null) {
//Authenticator.setDefault(new MyAuthenticator());

// String encodedUserPassword = new BASE64Encoder().encode((auth
// .getUserName()
// + ":" + new String(auth.getPassword())).getBytes());
// conn.setRequestProperty("Authorization", "Basic "
// + encodedUserPassword);
}

final URL dest = new URL(to);
URLConnection conn = null;
SpnegoHttpURLConnection spnego = null;
try {
spnego = new SpnegoHttpURLConnection("custom-client", auth.getUserName(), auth.getPassword().toString());
spnego.setRequestMethod("POST");
spnego.setRequestProperty("Content-Type",
ct == null ? ContentType.DEFAULT_CONTENT_TYPE.toString() : ct
.toString());
spnego.setRequestProperty("User-Agent", "https://wiseman.dev.java.net");
spnego.setRequestProperty("Accept", ContentType.ACCEPTABLE_CONTENT_TYPES);
conn = spnego.connect(dest, transfer(msg));
} catch (GSSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (PrivilegedActionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (LoginException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SOAPException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

//Authenticator.setDefault(new MyAuthenticator());
//conn = dest.openConnection();

//


final HttpURLConnection http = (HttpURLConnection) conn;
//http.setRequestMethod("POST");

return http;
}

// type of data can be Message or byte[], others will throw
// IllegalArgumentException
private static ByteArrayOutputStream transfer(final Object data)
throws IOException, SOAPException, JAXBException {
ByteArrayOutputStream os = null;
try {
os = new ByteArrayOutputStream();
if (data instanceof Message) {
((Message) data).writeTo(os);
} else if (data instanceof SOAPMessage) {
((SOAPMessage) data).writeTo(os);
} else if (data instanceof byte[]) {
os.write((byte[]) data);
} else {
throw new IllegalArgumentException("Type of data not handled: "
+ data.getClass().getName());
}
return os;
} finally {
if (os != null) {
os.close();
}
}
}

public static Addressing sendRequest(final SOAPMessage msg,
final String destination) throws IOException, SOAPException,
JAXBException {

if (LOG.isLoggable(Level.FINE))
LOG.fine("\n" + msg + "\n");
final HttpURLConnection http = initRequest(destination, ContentType
.createFromEncoding((String) msg
.getProperty(SOAPMessage.CHARACTER_SET_ENCODING)), msg);
//transfer(http, msg);
final Addressing response = readResponse(http);
if (LOG.isLoggable(Level.FINE)) {
if (response.getBody().hasFault())
LOG.fine("\n" + response + "\n");
else
LOG.fine("\n" + response + "\n");
}
return response;
}

public static Addressing sendRequest(final SOAPMessage msg,
final String destination, Entry... headers)
throws IOException, SOAPException, JAXBException {

if (LOG.isLoggable(Level.FINE))
LOG.fine("\n" + msg + "\n");
final HttpURLConnection http = initRequest(destination, ContentType
.createFromEncoding((String) msg
.getProperty(SOAPMessage.CHARACTER_SET_ENCODING)), msg);
// if (headers != null) {
// for (Entry entry : headers) {
// http.setRequestProperty(entry.getKey(), entry.getValue());
// }
// }
//
// transfer(http, msg);
final Addressing response = readResponse(http);
if (LOG.isLoggable(Level.FINE)) {
if (response.getBody().hasFault())
LOG.fine("\n" + response + "\n");
else
LOG.fine("\n" + response + "\n");
}
return response;
}

public static Addressing sendRequest(final Addressing msg,
final Entry... headers) throws IOException,
JAXBException, SOAPException {

if (LOG.isLoggable(Level.FINE))
LOG.fine("\n" + msg + "\n");
final HttpURLConnection http = initRequest(msg.getTo(), msg
.getContentType(), msg);

// if (headers != null) {
// for (Entry entry : headers) {
// http.setRequestProperty(entry.getKey(), entry.getValue());
// }
// }

//transfer(http, msg);
final Addressing response = readResponse(http);
if (LOG.isLoggable(Level.FINE)) {
if (response.getBody().hasFault())
LOG.fine("\n" + response + "\n");
else
LOG.fine("\n" + response + "\n");
}
response.setXmlBinding(msg.getXmlBinding());
return response;
}

public static HttpURLConnection createHttpConnection(String destination,
Object data) throws SOAPException, JAXBException, IOException {
final HttpURLConnection http = initRequest(destination, null, null);

return http;
}

private static HttpURLConnection initRequest(final String destination,
final ContentType contentType, Object msg) throws IOException {

final HttpURLConnection http = initConnection(destination, contentType, msg);
return http;
}

private static Addressing readResponse(final HttpURLConnection http)
throws IOException, SOAPException {

final InputStream is;
final int response = http.getResponseCode();
if (response == HttpURLConnection.HTTP_OK) {
is = http.getInputStream();
} else if (response == HttpURLConnection.HTTP_BAD_REQUEST
|| response == HttpURLConnection.HTTP_INTERNAL_ERROR) {
// read the fault from the error stream
is = http.getErrorStream();
} else {
final String detail = http.getResponseMessage();
throw new IOException(detail == null ? Integer.toString(response)
: detail);
}

final String responseType = http.getContentType();
final ContentType contentType = ContentType
.createFromHttpContentType(responseType);
if (contentType == null || !contentType.isAcceptable()) {
// dump the first 4k bytes of the response for help in debugging
if (LOG.isLoggable(Level.INFO)) {
final byte[] buffer = new byte[4096];
final int nread = is.read(buffer);
if (nread > 0) {
final ByteArrayOutputStream bos = new ByteArrayOutputStream(
buffer.length);
bos.write(buffer, 0, nread);
LOG.info("Response discarded: "
+ new String(bos.toByteArray()));
}
}
throw new IOException(
"Content-Type of response is not acceptable: "
+ responseType);
}

final Addressing addr;
try {
addr = new Addressing(is);
} finally {
if (is != null) {
is.close();
}
}

addr.setContentType(contentType);

return addr;
}

public static int sendResponse(final String to, final byte[] bits,
final ContentType contentType) throws IOException, SOAPException,
JAXBException {
final HttpURLConnection http = initConnection(to, contentType, bits);
return http.getResponseCode();
}

public static int sendResponse(final Addressing msg) throws IOException,
SOAPException, JAXBException {
final HttpURLConnection http = initConnection(msg.getTo(), msg
.getContentType(), msg);
return http.getResponseCode();
}
}

Supposedly you could achieve the same with the standard HTTP connection from Java 6 (see tutorial) but I couldn't get to work. So who knows... anyway the SPNEGO project's solution worked for me with an unencrypted setup on Windows Vista.

Monday, December 07, 2009

Spring and REST

After loving for more than two years Don Brown's fantastic REST plugin for Struts2 I decendent again into Spring's version of REST (see http://blog.springsource.com/2009/03/08/rest-in-spring-3-mvc/) My simple porting of Don Brown's demo app to Spring failed again (I tried 12 months ago to use some work from carbon five) Anyway why, why does it not work I thought I had it figured out:



package edu.ucsd.extension.springmvchelloworld;

import java.util.Collection;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;



@Controller
public class OrdersController {

private Order model = new Order();
private Collection list;
private OrdersService ordersService = new OrdersService();

// GET /orders/1
@RequestMapping("{orderId}")
public String show(@PathVariable String id, Model model) {
System.out.println("Showing order " + id);
setId(id);
model.addAttribute("order", getModel());
return "show";
}

// GET /orders
@RequestMapping
public String index(Model model) {
list = ordersService.getAll();
model.addAttribute("orders", list);
return "orders-index";
}

// GET /orders/1/edit
@RequestMapping("/{orderId}/edit")
public String edit(@PathVariable String id, Model model) {
this.setId(id);
model.addAttribute("order", getModel());
return "orders-edit";
}

// GET /orders/new
@RequestMapping("/new")
public String editNew(Model model) {
model.addAttribute("order", new Order());
return "orders-editNew";
}

// GET /orders/1/deleteConfirm
@RequestMapping("/{orderId}/deleteConfirm")
public String deleteConfirm(@PathVariable String id) {
this.setId(id);
return "orders-deleteConfirm";
}

// DELETE /orders/1
@RequestMapping(value="/{orderId}", method=RequestMethod.DELETE)
public String destroy(@PathVariable String id) {
ordersService.remove(id);
//addActionMessage("Order removed successfully");
return "orders-index";
}

// POST /orders
@RequestMapping(method=RequestMethod.POST)
public ModelAndView create(Order model) {
ordersService.save(model);
return new ModelAndView("orders-show", "model", getModel());
}

// PUT /orders/1
@RequestMapping(value="/{orderId}", method=RequestMethod.PUT)
public String update(@PathVariable String id, Order model) {
ordersService.save(model);
return "order-index";
}

public void setId(String id) {
if (id != null) {
this.model = ordersService.get(id);
}
}

public Object getModel() {
return (list != null ? list : model);
}

}


Well, I guess there is some other trick I am missing.

BTW: the repository in the POM file needs to look like:



org.springsource.maven.snapshot
Springframework milestone
http://maven.springframework.org/milestone

Sunday, November 15, 2009

The Passionate Programmer



I have been reading the The Passionate Programmer: Creating a Remarkable Career in Software Development (Pragmatic Life) and I think it is an absolute must read. I stumbled upon it on Matt's blog (see http://raibledesigns.com/rd/entry/the_passionate_programmer_by_chad) and I immediately liked the book. Chad is doing an incredible job explaining how to stay relevant and increase your career in "computers". The most impressive pieces are when he explains how to function in a corporate environment. "Make your manager happy he controls your pay" I can't say often enough how important that is. He also prefers a role maintaining software over some new project because oddly enough it gives you more freedom. His comparisons with the music scene are refreshing however probably not for everyone...

Anyway for $15 it's a must read :-)

Friday, August 28, 2009

Springing in Action

I have been asked to develop an online class for UCSD Extension covering Spring -- I am really excited and I will try to get it going soon --

On another note I started a new job with HP Software working among other things with JSF -- soon I can say I have seen all the web frameworks in the world :-)

Friday, June 26, 2009

SDCodeCamp

Saturday, 6/27 I will be talking on Scala and Spring -- check out their home page (http://www.socalcodecamp.com/)

Monday, May 25, 2009

IntelliJ always errors when loading Scala facet

The last couple of weeks every time I opened my project with IntelliJ I would get an error with the Scala plugin and the system would become unusable while indexing all the time.

I considered switching from my usual stack Struts 2/Spring/Hibernate to Lift/JPA and Eclipse but after I invalidated the cache (File -> Invalidate Caches) it seems to work again.

Friday, March 27, 2009

Simple cron like scheduler in Scala

Today in my Scala explorations I ran into the problem that I wanted some scheduler like Quarz or java.util.Timer -- that should be easy in scala I thought an came up with the following:



private val timedActor = actor {
//once a day
while (true) {
performTask
val c = Calendar.getInstance
c.set(Calendar.HOUR_OF_DAY, 0) //midnight
c.set(Calendar.MINUTE,0)
c.add(Calendar.DAY_OF_YEAR, 1) //next day
val sleepAmount = c.getTimeInMillis - Calendar.getInstance.getTimeInMillis
Thread.sleep(sleepAmount)
}
}


Pretty slick heavy weight (check the comment for a better implementation) -- they should include that in one of their libraries...