I am creating an application with Ktor and using Jetty. There is a rule, that, before taking a certain action, I need to check if the endpoint is up / down.
For this I created a function that I check according to the service.
suspend fun checkStatus(
target: Target,
login: String,
passwordLogin: String,
url: String
) {
when (target) {
Target.Elasticsearch -> {
val client = HttpClient(Jetty) {
install(Auth) {
basic {
username = login
password = passwordLogin
}
}
}
runCatching {
client.get<String>(url)
}.onFailure {
it.printStackTrace()
throw it
}
}
}
}
To decrease the size of the function I just used the example with elasticsearch. So I have a function that checks if the elasticsearch is up / down
suspend fun checkElasticStatus(
username: String,
password: String,
https: Boolean,
host: String,
port: String
) = checkStatus(
target = Target.Elasticsearch,
login = username,
passwordLogin = password,
url = if (https) "https://$host:$port" else "http://$host:$port"
)
So I use this function in the Controller, before continuing with certain logic.
fun Route.orchestration() {
route("/test") {
post {
runCatching {
checkElasticStatus(
environmentVariable(ev, "elk.username"),
environmentVariable(ev, "elk.password"),
environmentVariable(ev, "elk.https").toBoolean(),
environmentVariable(ev, "elk.host"),
environmentVariable(ev, "elk.port")
)
/** other codes **/
}
}
}
}
But I'm always getting the error:
org.eclipse.jetty.io.EofException at
org.eclipse.jetty.io.ChannelEndPoint.flush(ChannelEndPoint.java:283)
at org.eclipse.jetty.io.WriteFlusher.flush(WriteFlusher.java:422) at
org.eclipse.jetty.io.WriteFlusher.write(WriteFlusher.java:277) at
org.eclipse.jetty.io.AbstractEndPoint.write(AbstractEndPoint.java:381)
at
org.eclipse.jetty.http2.HTTP2Flusher.process(HTTP2Flusher.java:259)
at
org.eclipse.jetty.util.IteratingCallback.processing(IteratingCallback.java:241)
at
org.eclipse.jetty.util.IteratingCallback.iterate(IteratingCallback.java:223)
at
org.eclipse.jetty.http2.HTTP2Session.newStream(HTTP2Session.java:543)
at
io.ktor.client.engine.jetty.JettyHttpRequestKt$executeRequest$jettyRequest$1.invoke(JettyHttpRequest.kt:40)
at
io.ktor.client.engine.jetty.JettyHttpRequestKt$executeRequest$jettyRequest$1.invoke(JettyHttpRequest.kt)
at io.ktor.client.engine.jetty.UtilsKt.withPromise(utils.kt:14) at
io.ktor.client.engine.jetty.JettyHttpRequestKt.executeRequest(JettyHttpRequest.kt:39)
at
io.ktor.client.engine.jetty.JettyHttpRequestKt$executeRequest$1.invokeSuspend(JettyHttpRequest.kt)
at
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56) at
kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at
kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at
kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at
kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Caused by: java.nio.channels.AsynchronousCloseException at
java.base/sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:501)
at
org.eclipse.jetty.io.ChannelEndPoint.flush(ChannelEndPoint.java:263)
... 18 more
org.eclipse.jetty.io.EofException at
org.eclipse.jetty.io.ChannelEndPoint.flush(ChannelEndPoint.java:283)
at org.eclipse.jetty.io.WriteFlusher.flush(WriteFlusher.java:422) at
org.eclipse.jetty.io.WriteFlusher.write(WriteFlusher.java:277) at
org.eclipse.jetty.io.AbstractEndPoint.write(AbstractEndPoint.java:381)
at
org.eclipse.jetty.http2.HTTP2Flusher.process(HTTP2Flusher.java:259)
at
org.eclipse.jetty.util.IteratingCallback.processing(IteratingCallback.java:241)
at
org.eclipse.jetty.util.IteratingCallback.iterate(IteratingCallback.java:223)
at
org.eclipse.jetty.http2.HTTP2Session.newStream(HTTP2Session.java:543)
at
io.ktor.client.engine.jetty.JettyHttpRequestKt$executeRequest$jettyRequest$1.invoke(JettyHttpRequest.kt:40)
at
io.ktor.client.engine.jetty.JettyHttpRequestKt$executeRequest$jettyRequest$1.invoke(JettyHttpRequest.kt)
at io.ktor.client.engine.jetty.UtilsKt.withPromise(utils.kt:14) at
io.ktor.client.engine.jetty.JettyHttpRequestKt.executeRequest(JettyHttpRequest.kt:39)
at
io.ktor.client.engine.jetty.JettyHttpRequestKt$executeRequest$1.invokeSuspend(JettyHttpRequest.kt)
at
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56) at
kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at
kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at
kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at
kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Caused by: java.nio.channels.AsynchronousCloseException at
java.base/sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:501)
at
org.eclipse.jetty.io.ChannelEndPoint.flush(ChannelEndPoint.java:263)
... 18 more
Could anyone help me, please?
Jetty engine supports HTTP/2 protocol only.
org.eclipse.jetty.io.EofException is thrown when I make a request to resource which can only respond with HTTP/1.1, e.g. http://www.google.com.
However, the error is misleading and there is an issue in bugtracker about it.
Related
I am trying to get a Cloud Function to create a Cloud Task that will invoke a Cloud Function. Easy.
The flow and use case are very close to the official tutorial here.
I also looked at this article by Doug Stevenson and in particular its security section.
No luck, I am consistently getting a 16 (UNAUTHENTICATED) error in Cloud Task.
If I can trust what I see in the console it seems that Cloud Task is not attaching the OIDC token to the request:
Yet, in my code I do have the oidcToken object:
const { v2beta3, protos } = require("#google-cloud/tasks");
import {
PROJECT_ID,
EMAIL_QUEUE,
LOCATION,
EMAIL_SERVICE_ACCOUNT,
EMAIL_HANDLER,
} from "./../config/cloudFunctions";
export const createHttpTaskWithToken = async function (
payload: {
to_email: string;
templateId: string;
uid: string;
dynamicData?: Record<string, any>;
},
{
project = PROJECT_ID,
queue = EMAIL_QUEUE,
location = LOCATION,
url = EMAIL_HANDLER,
email = EMAIL_SERVICE_ACCOUNT,
} = {}
) {
const client = new v2beta3.CloudTasksClient();
const parent = client.queuePath(project, location, queue);
// Convert message to buffer.
const convertedPayload = JSON.stringify(payload);
const body = Buffer.from(convertedPayload).toString("base64");
const task = {
httpRequest: {
httpMethod: protos.google.cloud.tasks.v2.HttpMethod.POST,
url,
oidcToken: {
serviceAccountEmail: email,
audience: new URL(url).origin,
},
headers: {
"Content-Type": "application/json",
},
body,
},
};
try {
// Send create task request.
const request = { parent: parent, task: task };
const [response] = await client.createTask(request);
console.log(`Created task ${response.name}`);
return response.name;
} catch (error) {
if (error instanceof Error) console.error(Error(error.message));
return;
}
};
When logging the task object from the code above in Cloud Logging I can see that the service account is the one that I created for the purpose of this and that the Cloud Tasks are successfully created.
IAM:
And the function that the Cloud Task needs to invoke:
Everything seems to be there, in theory.
Any advice as to what I would be missing?
Thanks,
Your audience is incorrect. It must end by the function name. Here, you only have the region and the project https://<region>-<projectID>.cloudfunction.net/. Use the full Cloud Functions URL.
In loopback4, I have created custom authentication and authorization handlers, and wired them into the application. But the authorization handler is called only if the authentication function returns a UserProfile object, and skips authorization for an undefined user.
I want my Authorization handler to be called every time, no matter what the result of authentication is. I want to allow a non-authenticated call (don't know the user) to still flow through the authorization handler to let it judge whether to allow the call based on other factors besides the identity of the end user.
How do I make the Authorization handler be called every time?
export class MySequence implements SequenceHandler {
constructor(
#inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
#inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,
#inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
#inject(SequenceActions.SEND) public send: Send,
#inject(SequenceActions.REJECT) public reject: Reject,
#inject(AuthenticationBindings.AUTH_ACTION)
protected authenticateRequest: AuthenticateFn,
) {}
// see: https://loopback.io/doc/en/lb4/Loopback-component-authentication.html#adding-an-authentication-action-to-a-custom-sequence
async handle(context: RequestContext) {
try {
const {request, response} = context;
const route = this.findRoute(request);
//call authentication action
console.log(`request path = ${request.path}`);
await this.authenticateRequest(request); // HOW DO I CONTROL AUTHORIZATION CALL THAT FOLLOWS?
// Authentication step done, proceed to invoke controller
const args = await this.parseParams(request, route);
const result = await this.invoke(route, args);
this.send(response, result);
} catch (error) {
if (
error.code === AUTHENTICATION_STRATEGY_NOT_FOUND ||
error.code === USER_PROFILE_NOT_FOUND
) {
Object.assign(error, {statusCode: 401 /* Unauthorized */});
}
this.reject(context, error);
}
}
}
The full example of code is lengthy, so I have posted it in a gist here.
I found one way to invoke an authorization handler for every request. This still doesn't feel quite right, so there's probably a better solution.
In the application.ts you can setup default authorization metadata and supply a simpler voter that always votes DENY. After that, all controller calls will invoke authorization handlers, whether there is a #authorize() decorator present or not. Here's the setup:
// setup authorization
const noWayJose = (): Promise<AuthorizationDecision> => {
return new Promise(resolve => {
resolve(AuthorizationDecision.DENY);
});
};
this.component(AuthorizationComponent);
this.configure(AuthorizationBindings.COMPONENT).to({
defaultDecision: AuthorizationDecision.DENY,
precedence: AuthorizationDecision.ALLOW,
defaultMetadata: {
voters: [noWayJose],
},
});
this.bind('authorizationProviders.my-authorization-provider')
.toProvider(MyAuthorizationProvider)
.tag(AuthorizationTags.AUTHORIZER);
Now the /nope endpoint in the controller will have Authorization handlers evaluated even without the decorator.
export class YoController {
constructor() {}
#authorize({scopes: ['IS_COOL', 'IS_OKAY']})
#get('/yo')
yo(#inject(SecurityBindings.USER) user: UserProfile): string {
return `yo, ${user.name}!`;
}
#authorize({allowedRoles: [EVERYONE]})
#get('/sup')
sup(): string {
return `sup, dude.`;
}
#get('/nope')
nope(): string {
return `sorry dude.`;
}
#authorize({allowedRoles: [EVERYONE]})
#get('/yay')
yay(
#inject(SecurityBindings.USER, {optional: true}) user: UserProfile,
): string {
if (user) {
return `yay ${user.name}!`;
}
return `yay!`;
}
}
The other thing you have to do is not throw an error when authentication fails to find a user. That's because authorization does not get exercised until the invoke() function calls all the interceptors. So you have to swallow that error and let authorization have a say:
// from sequence.ts
async handle(context: RequestContext) {
try {
const {request, response} = context;
const route = this.findRoute(request);
//call authentication action
console.log(`request path = ${request.path}`);
try {
await this.authenticateRequest(request);
} catch (authenticationError) {
if (authenticationError.code === USER_PROFILE_NOT_FOUND) {
console.log(
"didn't find user. let's wait and see what authorization says.",
);
} else {
throw authenticationError;
}
}
// Authentication step done, proceed to invoke controller
const args = await this.parseParams(request, route);
// Authorization happens within invoke()
const result = await this.invoke(route, args);
this.send(response, result);
} catch (error) {
if (
error.code === AUTHENTICATION_STRATEGY_NOT_FOUND ||
error.code === USER_PROFILE_NOT_FOUND
) {
Object.assign(error, {statusCode: 401 /* Unauthorized */});
}
this.reject(context, error);
}
}
This is all suited to my use case. I wanted global defaults to have every endpoint be locked down with zero #authenticate and #authorize() decorators present. I plan to only add #authorize() to those places where I want to open things up. This is because I'm about to auto-generate a ton of controllers and will only want to expose a portion of the endpoints by hand.
Currently I've set up Apollo's web socket link like so:
const wsLink = new WebSocketLink({
uri: `ws://example.com/graphql?token=${getToken()}`,
options: {
reconnect: true,
connectionParams(): ConnectionParams {
return {
authToken: getToken(),
};
},
},
});
This works fine while the connection lasts, but fails when the connection needs to be re-established if the token in the query string has expired.
The way the infra I'm dealing with is set up requires this token to be set as a query param in the URI. How can I dynamically change the URI so that I may provide a new token when the connection needs to be re-established?
You can set property wsLink.subscriptionClient.url manually (or create a new subscriptionClient instance?) in function setContext https://www.apollographql.com/docs/link/links/context/.
For example:
import { setContext } from 'apollo-link-context'
...
const wsLink = your code...
const authLink = setContext(() => {
wsLink.subscriptionClient.url = `ws://example.com/graphql?token=${getToken()}`
})
...
const config = {
link: ApolloLink.from([
authLink,
wsLink
]),
...
}
I'm trying to use ADAL.js to authenticate against PowerBI in order to get an access_token and the embed_token needed to embed PowerBI reports/dashboards/tiles in a html/javascript-only "webpart". My adal-config looks like:
config = {
instance: 'https://login.windows.net/common/oauth2/authorize/',
tenant: 'tenant.onmicrosoft.com',
clientId: '05xxxxx-xxx-xxxx-xxxx-xxxxxxxxxxxx',
loginResource: "https://analysis.windows.net/powerbi/api",
postLogoutRedirectUri: window.location.origin,
cacheLocation: 'localStorage',
};
But I can't seem to find any access-token etc in the user.profile I get. I am obviously missing something but what.. :) Any help would be much appriciated
Looking at:
https://community.powerbi.com/t5/Developer/get-Access-token-using-js/m-p/350294
and also this:
https://community.powerbi.com/t5/Developer/How-to-Generate-Embed-Token-in-pure-JavaScript/td-p/350056
you can use ADAL.js to get the access token itself
window.config = {
instance: 'https://login.microsoftonline.com/',
tenant: 'common', //COMMON OR YOUR TENANT ID
clientId: 'XXXXX', //This is your client ID
redirectUri: 'XXXXXX', //This is your redirect URI
callback: userSignedIn,
popUp: true
};
var ADAL = new AuthenticationContext(config);
function signIn() {
ADAL.login();
}
function userSignedIn(err, token) {
console.log('userSignedIn called');
if (!err) {
showWelcomeMessage();
ADAL.acquireToken("https://analysis.windows.net/powerbi/api", function (error, token) {
// Handle ADAL Error
if (error || !token) {
printErrorMessage('ADAL Error Occurred: ' + error);
return;
}
}
Since it' apparently not possible to authenticate with LDAP on my BlackBerry App, I'm trying to use a kind of workaround. Instead of authenticate directly on the LDAP Server, I want to use a Web Service in between. So it looks like this
App --calls--> Web Service --calls--> LDAP Server
So the Web Service should take the username and password given from the Application and send it to the LDAP Server. If its possible to sign in, the Web Service gets a TRUE as response and forward it to the App.
That's how it should work. But at the moment, when I call the Web Service from the App, I get following error:
SoapFault - faultcode: 'S:Server' faultstring:
'java.lang.NullPointerException' faultactor: 'null' detail:
org.kxml2.kdom.Node#21e05a11
Seems like a Server problem but I don't know where :(
Well, that's the Web Service I'm using:
import javax.ejb.Stateless;
import javax.jws.WebService;
import com.novell.ldap.LDAPConnection;
import com.novell.ldap.LDAPException;
#Stateless
#WebService()
public class ldapServiceBean implements ldapService {
#Override
public String error() {
// TODO Auto-generated method stub
return null;
}
#Override
public boolean ldapLogin(String username, String password) {
int ldapPort = LDAPConnection.DEFAULT_PORT;
int ldapVersion = LDAPConnection.LDAP_V3;
String ldapHost = "dc1.somehost ";
String loginDN =
"CN="+username+",OU=employee,OU=user,DC=somehost";
byte[] passwordBytes = password.getBytes();
LDAPConnection lc = new LDAPConnection();
try {
// connect to the server
lc.connect( ldapHost, ldapPort );
// authenticate to the server
lc.bind( ldapVersion, loginDN, passwordBytes );
System.out.println("Bind successful");
return true;
}
catch( LDAPException e ) {
if ( e.getResultCode() == LDAPException.NO_SUCH_OBJECT ) {
System.err.println( "Error: No such entry" );
} else if ( e.getResultCode() ==
LDAPException.NO_SUCH_ATTRIBUTE ) {
System.err.println( "Error: No such attribute" );
} else {
System.err.println( "Error: " + e.toString() );
}
}
return false;
}
And that's the method calling the Web Service
private static final String SOAP_ACTION = "";
private static final String METHOD_NAME = "ldapLogin";
private static final String NAMESPACE = "http://ldapproxy.somehost/";
private static final String URL = "http://myIP:8080/LDAPProxy/ldapServiceBeanService";
...
public boolean login(String username, String password) {
SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
//SoapObject
request.addProperty("username", username);
request.addProperty("password", password);
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
//envelope.dotNet = true;
//envelope.bodyOut = request;
envelope.setOutputSoapObject(request);
HttpTransport httpTransport = new HttpTransport(URL);
try
{
httpTransport.call(SOAP_ACTION, envelope);
System.out.println("request: " + httpTransport.requestDump);
resultsRequestSOAP = (SoapObject) envelope.getResponse();
return true;
}catch(SoapFault sF){
String error = sF.toString();
Dialog.alert(error);
}
catch (Exception aE)
{
Dialog.alert("Connection failed");
aE.printStackTrace ();
}
return false;
}
What I found out so far:
It seems that the webservice don't receives the username and password property. As I print them I get:
`CN=null, OU=employee, OU=...`
Like I've read at this post Web service recieves null parameters from application using ksoap method it seems ksoap have a problem with colons. I changed my NAMESPACE but without any success. Maybe I need to change my URL too. But how would I do this while I still need to use localhost ?
As always when doing LDAP bind testing this way, recall that the standard requires that a bind of a username, no password, is a successful Anonymous bind, so therefore you MUST validate for this case (empty password) on login attempts.